Ekspresi Generator vs. Pemahaman Daftar

412

Kapan Anda harus menggunakan ekspresi generator dan kapan Anda harus menggunakan daftar pemahaman dalam Python?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]
Hanya baca
sumber
28
[exp for x in iter]hanya bisa menjadi gula untuk list((exp for x in iter))? atau apakah ada perbedaan eksekusi?
b0fh
1
rasanya saya punya pertanyaan yang relevan, jadi ketika menggunakan hasil dapat kita gunakan hanya ekspresi generator dari suatu fungsi atau kita harus menggunakan hasil untuk fungsi untuk mengembalikan objek generator?
28
@ b0fh Jawabannya sangat terlambat untuk komentar Anda: di Python2 ada perbedaan kecil, variabel loop akan bocor keluar dari pemahaman daftar, sementara ekspresi generator tidak akan bocor. Bandingkan X = [x**2 for x in range(5)]; print xdengan Y = list(y**2 for y in range(5)); print y, yang kedua akan memberikan kesalahan. Dalam Python3, pemahaman daftar memang gula sintaksis untuk ekspresi generator diumpankan list()seperti yang Anda harapkan, sehingga variabel loop tidak akan lagi bocor .
Bas Swinckels
13
Saya sarankan membaca PEP 0289 . Disimpulkan oleh "PEP ini memperkenalkan ekspresi generator sebagai generalisasi efisien tinggi, efisiensi daftar pemahaman dan generator" . Ini juga memiliki contoh berguna kapan menggunakannya.
icc97
5
@ icc97 Saya juga terlambat delapan tahun ke pesta, dan tautan PEP sempurna. Terima kasih telah membuatnya mudah ditemukan!
eenblam

Jawaban:

283

Jawaban John adalah baik (daftar itu lebih baik bila Anda ingin mengulangi sesuatu berulang kali). Namun, perlu dicatat bahwa Anda harus menggunakan daftar jika Anda ingin menggunakan salah satu metode daftar. Misalnya, kode berikut tidak akan berfungsi:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

Pada dasarnya, gunakan ekspresi generator jika semua yang Anda lakukan adalah iterasi satu kali. Jika Anda ingin menyimpan dan menggunakan hasil yang dihasilkan, maka Anda mungkin lebih baik dengan pemahaman daftar.

Karena kinerja adalah alasan paling umum untuk memilih satu di atas yang lain, saran saya adalah jangan khawatir tentang itu dan hanya memilih satu; jika Anda menemukan bahwa program Anda berjalan terlalu lambat, maka dan hanya kemudian Anda harus kembali dan khawatir tentang penyetelan kode Anda.

Eli Courtwright
sumber
70
Kadang-kadang Anda harus menggunakan generator - misalnya, jika Anda menulis coroutine dengan penjadwalan kooperatif menggunakan hasil. Tetapi jika Anda melakukan itu, Anda mungkin tidak menanyakan pertanyaan ini;)
ephemient
12
Saya tahu ini sudah tua, tapi saya pikir perlu dicatat bahwa generator (dan setiap iterable) dapat ditambahkan ke daftar dengan ekstensi: a = [1, 2, 3] b = [4, 5, 6] a.extend(b)- a sekarang akan [1, 2, 3, 4, 5, 6]. (Bisakah Anda menambahkan baris baru dalam komentar ??)
jarvisteve
12
@jarvisteve teladan Anda memungkiri kata-kata yang Anda ucapkan. Ada juga poin bagus di sini. Daftar dapat diperpanjang dengan generator, tetapi kemudian tidak ada gunanya menjadikannya generator. Generator tidak dapat diperpanjang dengan daftar, dan generator tidak cukup iterables. a = (x for x in range(0,10)), b = [1,2,3]contohnya. a.extend(b)melempar pengecualian. b.extend(a)akan mengevaluasi semua dari, dalam hal ini tidak ada gunanya menjadikannya generator di tempat pertama.
Slater Victoroff
4
@ SlaterTyranus Anda 100% benar, dan saya meningkatkan akurasi Anda. namun, saya pikir komentarnya adalah jawaban yang tidak berguna untuk pertanyaan OP karena itu akan membantu mereka yang menemukan diri mereka di sini karena mereka mengetik sesuatu seperti 'gabungkan generator dengan pemahaman daftar' ke dalam mesin pencari.
rbp
1
Bukankah alasan untuk menggunakan generator untuk beralih sekali saja (mis. Kekhawatiran saya tentang kurangnya memori mengesampingkan kekhawatiran saya tentang "mengambil" nilai satu per satu ) mungkin masih berlaku ketika iterating berulang kali? Saya akan mengatakan itu mungkin membuat daftar lebih berguna, tetapi apakah itu cukup untuk melebihi masalah memori adalah sesuatu yang lain.
Rob Grant
181

Iterasi atas ekspresi generator atau pemahaman daftar akan melakukan hal yang sama. Namun, pemahaman daftar akan membuat seluruh daftar di memori terlebih dahulu sementara ekspresi generator akan membuat item dengan cepat, sehingga Anda dapat menggunakannya untuk urutan yang sangat besar (dan juga tak terbatas!).

dF.
sumber
39
+1 untuk yang tak terbatas. Anda tidak dapat melakukannya dengan daftar, terlepas dari seberapa kecil Anda peduli dengan kinerja.
Paul Draper
Bisakah Anda membuat generator yang tak terbatas menggunakan metode pemahaman?
AnnanFay
5
@Annan Hanya jika Anda sudah memiliki akses ke generator tak terbatas lainnya. Sebagai contoh, itertools.count(n)adalah urutan bilangan bulat tak terbatas, mulai dari n, sehingga (2 ** item for item in itertools.count(n))akan menjadi urutan tak terbatas dari kekuatan 2mulai dari 2 ** n.
Kevin
2
Generator menghapus item dari memori setelah iterasi selesai. Jadi, cepat jika Anda memiliki data besar, Anda hanya ingin menampilkannya, misalnya. Ini bukan babi memori. dengan generator item diproses 'sesuai kebutuhan'. jika Anda ingin bertahan pada daftar atau beralih lagi (jadi simpan item) kemudian gunakan pemahaman daftar.
j2emanue
102

Gunakan pemahaman daftar ketika hasilnya perlu diulang beberapa kali, atau di mana kecepatan sangat penting. Gunakan ekspresi generator di mana kisarannya besar atau tidak terbatas.

Lihat Ekspresi generator dan daftar pemahaman untuk info lebih lanjut.

John Millikin
sumber
2
Ini mungkin sedikit di luar topik, tapi sayangnya "tidak dapat ditelusuri" ... Apa yang akan "terpenting" dalam konteks ini? Saya bukan penutur asli bahasa Inggris ... :)
Guillermo Ares
6
@GuillermoAres ini adalah hasil langsung dari "googling" untuk arti yang terpenting: lebih penting daripada yang lain; tertinggi.
Sнаđошƒаӽ
1
Jadi, listsapakah lebih cepat dari generatorekspresi? Dari membaca jawaban dF, terlihat bahwa itu adalah sebaliknya.
Hassan Baig
1
Mungkin lebih baik untuk mengatakan bahwa pemahaman daftar lebih cepat ketika kisarannya kecil, tetapi ketika skalanya meningkat itu menjadi lebih berharga untuk menghitung nilai-nilai dengan cepat - tepat pada waktunya untuk penggunaannya. Itulah yang dilakukan ekspresi generator.
Kyle
59

Poin penting adalah bahwa pemahaman daftar membuat daftar baru. Generator menciptakan objek yang dapat diubah yang akan "memfilter" bahan sumber saat Anda mengkonsumsi bit.

Bayangkan Anda memiliki file log 2TB yang disebut "hugefile.txt", dan Anda menginginkan konten dan panjang untuk semua baris yang dimulai dengan kata "ENTRY".

Jadi Anda mencoba memulai dengan menulis daftar pemahaman:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

Ini menyeruput seluruh file, memproses setiap baris, dan menyimpan baris yang cocok dalam array Anda. Oleh karena itu array ini dapat memuat konten hingga 2TB. Itu banyak RAM, dan mungkin tidak praktis untuk keperluan Anda.

Jadi alih-alih, kita dapat menggunakan generator untuk menerapkan "filter" ke konten kita. Tidak ada data yang benar-benar dibaca sampai kita mulai mengulangi hasilnya.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

Bahkan satu baris pun belum dibaca dari file kami. Bahkan, katakanlah kami ingin memfilter hasil kami lebih jauh:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

Masih belum ada yang dibaca, tetapi kami telah menentukan sekarang dua generator yang akan bertindak berdasarkan data kami seperti yang kami inginkan.

Mari kita menulis baris yang difilter ke file lain:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

Sekarang kita membaca file input. Ketika forloop kami terus meminta jalur tambahan, long_entriesgenerator meminta jalur dari entry_linesgenerator, hanya mengembalikan yang panjangnya lebih dari 80 karakter. Dan pada gilirannya, entry_linesgenerator meminta baris (difilter seperti yang ditunjukkan) dari logfileiterator, yang pada gilirannya membaca file.

Jadi, alih-alih "mendorong" data ke fungsi output Anda dalam bentuk daftar yang terisi penuh, Anda memberikan fungsi output cara untuk "menarik" data hanya saat dibutuhkan. Ini dalam kasus kami jauh lebih efisien, tetapi tidak cukup fleksibel. Generator adalah satu arah, satu lintasan; data dari file log yang telah kita baca segera dibuang, jadi kita tidak bisa kembali ke baris sebelumnya. Di sisi lain, kita tidak perlu khawatir tentang menyimpan data setelah kita selesai dengan itu.

tylerl
sumber
46

Manfaat dari ekspresi generator adalah ia menggunakan lebih sedikit memori karena tidak membangun seluruh daftar sekaligus. Ekspresi generator paling baik digunakan ketika daftar adalah perantara, seperti menjumlahkan hasil, atau membuat dict dari hasil.

Sebagai contoh:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

Keuntungannya adalah bahwa daftar tersebut tidak sepenuhnya dihasilkan, dan dengan demikian sedikit memori yang digunakan (dan juga harus lebih cepat)

Namun, Anda harus menggunakan pemahaman daftar ketika produk akhir yang diinginkan adalah daftar. Anda tidak akan menyimpan memeory menggunakan ekspresi generator, karena Anda ingin daftar yang dihasilkan. Anda juga mendapatkan manfaat dari dapat menggunakan salah satu fungsi daftar seperti diurutkan atau dibalik.

Sebagai contoh:

reversed( [x*2 for x in xrange(256)] )
Membuang
sumber
9
Ada petunjuk untuk Anda dalam bahasa bahwa ekspresi generator dimaksudkan untuk digunakan dengan cara itu. Kalah kurung! sum(x*2 for x in xrange(256))
u0b34a0f6ae
8
sorteddan reversedbekerja dengan baik pada setiap iterable, termasuk ekspresi generator.
marr75
1
Jika Anda dapat menggunakan 2.7 dan di atas, contoh dict () akan terlihat lebih baik sebagai pemahaman dict (PEP untuk yang lebih tua dari generator ekspresi PEP, tetapi membutuhkan waktu lebih lama untuk mendarat)
Jürgen A. Erhard
14

Saat membuat generator dari objek yang bisa berubah-ubah (seperti daftar), ketahuilah bahwa generator akan dievaluasi pada keadaan daftar saat menggunakan generator, bukan pada saat pembuatan generator:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

Jika ada kemungkinan daftar Anda diubah (atau objek yang bisa berubah di dalam daftar itu) tetapi Anda memerlukan status saat membuat generator, Anda harus menggunakan pemahaman daftar.

orang aneh
sumber
1
Dan ini harus menjadi jawaban yang diterima. Jika data Anda lebih besar dari memori yang tersedia, Anda harus selalu menggunakan generator meskipun pengulangan daftar dalam memori mungkin lebih cepat (tetapi Anda tidak memiliki cukup memori untuk melakukannya).
Marek Marczak
4

Kadang-kadang Anda dapat pergi dengan fungsi tee dari itertools , ia mengembalikan beberapa iterator untuk generator yang sama yang dapat digunakan secara mandiri.

Jacob Rigby
sumber
4

Saya menggunakan modul Hadoop Mincemeat . Saya pikir ini adalah contoh yang bagus untuk mencatat:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

Di sini generator mengeluarkan angka dari file teks (sebesar 15GB) dan menerapkan matematika sederhana pada angka-angka tersebut menggunakan pengurangan peta Hadoop. Jika saya tidak menggunakan fungsi hasil, tetapi sebagai daftar pemahaman, itu akan memakan waktu lebih lama menghitung jumlah dan rata-rata (belum lagi kompleksitas ruang).

Hadoop adalah contoh yang bagus untuk menggunakan semua keunggulan Generator.

Murphy
sumber