Mengapa tidak ada pemahaman tuple di Python?

340

Seperti yang kita semua tahu, ada pemahaman daftar, seperti

[i for i in [1, 2, 3, 4]]

dan ada pemahaman kamus, seperti

{i:j for i, j in {1: 'a', 2: 'b'}.items()}

tapi

(i for i in (1, 2, 3))

akan berakhir di generator, bukan tuplepemahaman. Mengapa demikian?

Dugaan saya adalah bahwa a tupletidak berubah, tetapi ini sepertinya bukan jawabannya.

Shady Xu
sumber
16
Ada juga satu set pemahaman - yang sangat mirip dengan pemahaman dict ...
mgilson
3
Ada kesalahan sintaksis dalam kode Anda: {i:j for i,j in {1:'a', 2:'b'}}seharusnya{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose
@InbarRose Terima kasih telah menunjukkannya -.-
Shady Xu
Hanya demi keturunan, ada diskusi tentang hal ini terjadi dalam Obrolan Python
Inbar Rose
Ternyata ada. stackoverflow.com/a/51811147/9627166
Super S

Jawaban:

471

Anda dapat menggunakan ekspresi generator:

tuple(i for i in (1, 2, 3))

tetapi tanda kurung sudah diambil untuk… ekspresi generator.

Martijn Pieters
sumber
15
Dengan argumen ini, kita bisa mengatakan daftar-pemahaman tidak perlu terlalu: list(i for i in (1,2,3)). Saya benar-benar berpikir itu hanya karena tidak ada sintaks yang bersih untuk itu (atau setidaknya tidak ada yang memikirkannya)
mgilson
79
Pemahaman daftar atau set atau dikte hanyalah sintaksis gula untuk menggunakan ekspresi generator yang menghasilkan jenis tertentu. list(i for i in (1, 2, 3))adalah ekspresi generator yang menampilkan daftar, set(i for i in (1, 2, 3))mengeluarkan satu set. Apakah itu berarti sintaks pemahaman tidak diperlukan? Mungkin tidak, tapi ini sangat berguna. Untuk kasus yang jarang Anda membutuhkan tuple sebagai gantinya, ekspresi generator akan melakukan, jelas, dan tidak memerlukan penemuan penjepit atau braket lain.
Martijn Pieters
16
Jawabannya jelas karena sintaks tuple dan tanda kurung ambigu
Charles Salvia
19
Perbedaan antara menggunakan pemahaman dan menggunakan generator konstruktor + lebih dari halus jika Anda peduli tentang kinerja. Pemahaman menghasilkan konstruksi yang lebih cepat dibandingkan dengan menggunakan generator yang diteruskan ke konstruktor. Dalam kasus terakhir Anda membuat dan menjalankan fungsi dan fungsi mahal dengan Python. [thing for thing in things]membangun daftar lebih cepat daripada list(thing for thing in things). Pemahaman tuple tidak akan sia-sia; tuple(thing for thing in things)memiliki masalah latensi dan tuple([thing for thing in things])dapat memiliki masalah memori.
Justin Turner Arthur
9
@ MartijnPieters, Dapatkah Anda berpotensi menulis ulang A list or set or dict comprehension is just syntactic sugar to use a generator expression? Ini menyebabkan kebingungan oleh orang-orang yang melihat ini sebagai cara yang setara untuk mencapai tujuan. Ini bukan gula sintaksis secara teknis karena prosesnya sebenarnya berbeda, bahkan jika produk akhirnya sama.
jpp
77

Raymond Hettinger (salah satu pengembang inti Python) mengatakan ini tentang tuple dalam tweet baru - baru ini :

#python tip: Secara umum, daftar untuk pengulangan; tuple untuk struct. Daftarnya homogen; tupel heterogen. Daftar untuk panjang variabel.

Ini (bagi saya) mendukung gagasan bahwa jika item-item dalam suatu urutan cukup terkait untuk dihasilkan oleh generator, well, maka itu haruslah sebuah daftar. Meskipun tuple dapat diubah dan sepertinya hanya daftar yang tidak dapat diubah, itu benar-benar setara dengan Python dari struct C:

struct {
    int a;
    char b;
    float c;
} foo;

struct foo x = { 3, 'g', 5.9 };

menjadi dengan Python

x = (3, 'g', 5.9)
chepner
sumber
26
Properti immutibilitas dapat menjadi penting dan seringkali merupakan alasan yang baik untuk menggunakan tuple ketika Anda biasanya menggunakan daftar. Misalnya, jika Anda memiliki daftar 5 angka yang ingin Anda gunakan sebagai kunci ke dikt, maka tuple adalah cara yang harus dilakukan.
pavon
Itu tip yang bagus dari Raymond Hettinger. Saya masih akan mengatakan ada kasus penggunaan untuk menggunakan konstruktor tuple dengan generator, seperti membongkar struktur lain, mungkin lebih besar, menjadi yang lebih kecil dengan mengulangi attrs yang Anda tertarik untuk mengkonversi ke catatan tuple.
dave
2
@dave Anda mungkin hanya dapat menggunakan operator.itemgetterdalam kasus itu.
chepner
@ chepner, begitu. Itu cukup dekat dengan apa yang saya maksud. Itu mengembalikan callable jadi jika saya hanya perlu melakukannya sekali saya tidak melihat banyak kemenangan vs hanya menggunakan tuple(obj[item] for item in items)secara langsung. Dalam kasus saya, saya menanamkan ini ke dalam daftar pemahaman untuk membuat daftar rekaman tuple. Jika saya perlu melakukan ini berulang kali di seluruh kode maka itemgetter tampak hebat. Mungkin itemgetter akan lebih idiomatis?
dave
Saya melihat hubungan antara frozenset dan diset analog dengan tuple dan daftar. Ini kurang tentang heterogenitas dan lebih banyak tentang kekekalan - frozenset dan tuple dapat menjadi kunci untuk kamus, daftar dan set tidak bisa karena mutabilitas mereka.
polyglot
56

Karena Python 3.5 , Anda juga dapat menggunakan *sintaks percikan unpacking untuk membongkar ekspresi generator:

*(x for x in range(10)),
czheo
sumber
2
Ini hebat (dan berhasil), tetapi saya tidak dapat menemukan di mana pun itu didokumentasikan! Apakah Anda memiliki tautan?
felixphew
8
Catatan: Sebagai detail implementasi, ini pada dasarnya sama dengan melakukan tuple(list(x for x in range(10)))( jalur kode identik , dengan keduanya membangun a list, dengan satu-satunya perbedaan adalah bahwa langkah terakhir adalah membuat tupledari listdan membuang listsaat tuplekeluaran) diperlukan). Berarti Anda tidak benar-benar menghindari sepasang temporer.
ShadowRanger
4
Untuk memperluas komentar @ShadowRanger, inilah pertanyaan di mana mereka menunjukkan bahwa sintaks literal tuple + tuple sebenarnya sedikit lebih lambat daripada mengirimkan ekspresi generator ke konstruktor tuple.
Lucubrator
Saya mencoba ini dengan Python 3.7.3 dan *(x for x in range(10))tidak berhasil. Saya mengerti SyntaxError: can't use starred expression here. Namun tuple(x for x in range(10))berhasil.
Ryan H.
4
@RyanH. Anda harus memberi koma pada akhirnya.
czheo
27

Seperti yang macmdisebutkan poster lain , cara tercepat untuk membuat tuple dari generator adalah tuple([generator]).


Perbandingan Kinerja

  • Pemahaman daftar:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Tuple dari pemahaman daftar:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
  • Tuple dari generator:

    $ python3 -m timeit "a = tuple(i for i in range(1000))"
    10000 loops, best of 3: 50.4 usec per loop
  • Tuple dari membongkar:

    $ python3 -m timeit "a = *(i for i in range(1000)),"
    10000 loops, best of 3: 52.7 usec per loop

Versi python saya :

$ python3 --version
Python 3.6.3

Jadi, Anda harus selalu membuat tuple dari daftar pemahaman kecuali kinerja tidak menjadi masalah.

Tom
sumber
10
Catatan: tupledari listcomp membutuhkan penggunaan memori puncak berdasarkan ukuran gabungan dari final tupledan list. tupledari genexpr, sementara lebih lambat, berarti Anda hanya membayar untuk final tuple, tidak sementara list(genexpr itu sendiri menempati memori yang kurang lebih tetap). Biasanya tidak bermakna, tetapi bisa jadi penting ketika ukuran yang terlibat sangat besar.
ShadowRanger
25

Pemahaman bekerja dengan mengulang atau mengulangi item dan menugaskannya ke dalam wadah, Tuple tidak dapat menerima tugas.

Setelah Tuple dibuat, itu tidak dapat ditambahkan ke, diperpanjang, atau ditugaskan. Satu-satunya cara untuk memodifikasi Tuple adalah jika salah satu objek itu sendiri dapat ditugaskan (adalah wadah non-tuple). Karena Tuple hanya memegang referensi ke objek semacam itu.

Juga - tuple memiliki konstruktor sendiri tuple()yang dapat Anda berikan iterator apa pun. Yang berarti bahwa untuk membuat tuple, Anda dapat melakukan:

tuple(i for i in (1,2,3))
Inbar Rose
sumber
9
Dalam beberapa hal saya setuju (tentang itu tidak perlu karena daftar akan dilakukan), tetapi dengan cara lain saya tidak setuju (tentang alasannya karena tidak dapat diubah). Dalam beberapa hal, lebih masuk akal untuk memiliki pemahaman terhadap objek yang tidak berubah. yang melakukan lst = [x for x in ...]; x.append()?
mgilson
@ mgilson Saya tidak yakin bagaimana hubungannya dengan apa yang saya katakan?
Inbar Rose
2
@ mgilson jika tuple tidak dapat diubah yang berarti implementasi yang mendasarinya tidak dapat "menghasilkan" tuple ("generasi" yang menyiratkan pembangunan satu per satu). abadi berarti Anda tidak dapat membangun yang dengan 4 buah dengan mengubah yang dengan 3 buah. alih-alih, Anda menerapkan tuple "generasi" dengan membuat daftar, sesuatu yang dirancang untuk generasi, lalu membangun tuple sebagai langkah terakhir, dan membuang daftar. Bahasa mencerminkan realitas ini. Pikirkan tuple sebagai C struct.
Scott
2
meskipun akan masuk akal jika sintaksis sintaksis pemahaman berfungsi untuk tupel, karena Anda tidak dapat menggunakan tupel sampai pemahaman dikembalikan. Secara efektif itu tidak bertindak seperti bisa berubah, melainkan pemahaman tuple bisa berperilaku seperti menambahkan string.
uchuugaka
12

Dugaan terbaik saya adalah mereka kehabisan kurung dan tidak berpikir itu akan cukup berguna untuk warwar menambahkan sintaks "jelek" ...

mgilson
sumber
1
Kurung sudut tidak digunakan.
uchuugaka
@uchuugaka - Tidak sepenuhnya. Mereka digunakan untuk operator perbandingan. Mungkin masih bisa dilakukan tanpa ambiguitas, tetapi mungkin tidak sepadan dengan usaha ...
mgilson
3
@uchuugaka Perlu dicatat bahwa {*()}, meskipun jelek, berfungsi sebagai himpunan literal kosong!
MI Wright
1
Ugh. Dari sudut pandang estetika, saya pikir saya sebagian untuk set():)
mgilson
1
@ QuantumMechanic: Ya, itu intinya; generalisasi pembongkaran memungkinkan "set literal" kosong. Catatan yang {*[]}benar-benar kalah dengan opsi lain; string kosong dan kosong tuple, tidak berubah, adalah lajang, sehingga tidak ada sementara diperlukan untuk membangun kosong set. Sebaliknya, yang kosong listbukan singleton, jadi Anda benar-benar harus membangunnya, menggunakannya untuk membangunnya set, lalu menghancurkannya, kehilangan keuntungan kinerja sepele apa pun yang disediakan oleh operator monyet bermata satu.
ShadowRanger
8

Tuples tidak dapat secara efisien ditambahkan seperti daftar.

Jadi pemahaman tuple perlu menggunakan daftar secara internal dan kemudian dikonversi menjadi tuple.

Itu akan sama dengan apa yang Anda lakukan sekarang: tuple ([pemahaman])

macm
sumber
3

Kurung tidak membuat tupel. alias satu = (dua) bukan tupel. Satu-satunya jalan keluar adalah salah satu = (dua,) atau satu = tupel (dua). Jadi solusinya adalah:

tuple(i for i in myothertupleorlistordict) 
ilias iliadis
sumber
bagus. hampir sama.
uchuugaka
-1

Saya percaya itu hanya demi kejelasan, kami tidak ingin mengacaukan bahasa dengan terlalu banyak simbol yang berbeda. Juga tuplepemahaman tidak pernah diperlukan , daftar hanya dapat digunakan sebagai gantinya dengan perbedaan kecepatan yang dapat diabaikan, tidak seperti pemahaman dict yang bertentangan dengan pemahaman daftar.

jamylak
sumber
-2

Kami dapat menghasilkan tupel dari pemahaman daftar. Yang berikut menambahkan dua angka secara berurutan ke dalam tuple dan memberikan daftar dari angka 0-9.

>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Rohit Malgaonkar
sumber