Catatan: posting ini mengasumsikan sintaksis Python 3.x. †
Sebuah Generator hanyalah sebuah fungsi yang mengembalikan sebuah objek di mana Anda dapat memanggil next
, sehingga untuk setiap panggilan itu mengembalikan beberapa nilai, sampai menimbulkan StopIteration
pengecualian, menandakan bahwa semua nilai telah dihasilkan. Objek semacam itu disebut iterator .
Fungsi normal mengembalikan nilai tunggal menggunakan return
, seperti di Jawa. Dalam Python, bagaimanapun, ada alternatif, yang disebut yield
. Menggunakan yield
di mana saja dalam suatu fungsi menjadikannya generator. Perhatikan kode ini:
>>> def myGen(n):
... yield n
... yield n + 1
...
>>> g = myGen(6)
>>> next(g)
6
>>> next(g)
7
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Seperti yang Anda lihat, myGen(n)
adalah fungsi yang menghasilkan n
dan n + 1
. Setiap panggilan untuk next
menghasilkan nilai tunggal, sampai semua nilai telah dihasilkan. for
loop memanggil next
di latar belakang, dengan demikian:
>>> for n in myGen(6):
... print(n)
...
6
7
Demikian juga ada ekspresi generator , yang menyediakan sarana untuk secara ringkas menggambarkan jenis umum generator tertentu:
>>> g = (n for n in range(3, 5))
>>> next(g)
3
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Perhatikan bahwa ekspresi generator sangat mirip dengan pemahaman daftar :
>>> lc = [n for n in range(3, 5)]
>>> lc
[3, 4]
Perhatikan bahwa objek generator dihasilkan sekali , tetapi kode tidak dijalankan sekaligus. Hanya panggilan untuk next
benar-benar mengeksekusi (bagian dari) kode. Eksekusi kode dalam generator berhenti begitu sebuah yield
pernyataan telah tercapai, yang padanya ia mengembalikan nilai. Panggilan berikutnya untuk next
kemudian menyebabkan eksekusi untuk melanjutkan dalam keadaan di mana generator dibiarkan setelah yang terakhir yield
. Ini adalah perbedaan mendasar dengan fungsi-fungsi reguler: yang selalu memulai eksekusi di "atas" dan membuang status mereka setelah mengembalikan nilai.
Ada banyak hal yang bisa dikatakan tentang masalah ini. Misalnya dimungkinkan untuk send
data kembali ke generator ( referensi ). Tapi itu adalah sesuatu yang saya sarankan Anda tidak melihat ke dalam sampai Anda memahami konsep dasar generator.
Sekarang Anda mungkin bertanya: mengapa menggunakan generator? Ada beberapa alasan bagus:
- Konsep tertentu dapat dideskripsikan dengan lebih ringkas menggunakan generator.
- Alih-alih membuat fungsi yang mengembalikan daftar nilai, orang dapat menulis generator yang menghasilkan nilai dengan cepat. Ini berarti bahwa tidak ada daftar yang perlu dibangun, yang berarti bahwa kode yang dihasilkan lebih efisien memori. Dengan cara ini orang bahkan dapat menggambarkan aliran data yang hanya akan terlalu besar untuk muat dalam memori.
Generator memungkinkan cara alami untuk menggambarkan aliran tanpa batas . Pertimbangkan misalnya angka Fibonacci :
>>> def fib():
... a, b = 0, 1
... while True:
... yield a
... a, b = b, a + b
...
>>> import itertools
>>> list(itertools.islice(fib(), 10))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Kode ini digunakan itertools.islice
untuk mengambil sejumlah elemen hingga dari aliran tanpa batas. Anda disarankan untuk melihat dengan baik fungsi-fungsi dalam itertools
modul, karena mereka adalah alat penting untuk menulis generator yang canggih dengan sangat mudah.
† Tentang Python <= 2.6: dalam contoh di atas next
adalah fungsi yang memanggil metode __next__
pada objek yang diberikan. Dalam Python <= 2.6 kita menggunakan teknik yang sedikit berbeda, yaitu o.next()
alih-alih next(o)
. Python 2.7 memiliki next()
panggilan .next
sehingga Anda tidak perlu menggunakan yang berikut ini di 2.7:
>>> g = (n for n in range(3, 5))
>>> g.next()
3
send
data ke generator. Setelah Anda melakukannya, Anda memiliki 'coroutine'. Sangat sederhana untuk menerapkan pola seperti Konsumen / Produsen yang disebutkan dengan coroutine karena mereka tidak memerlukanLock
s dan oleh karena itu tidak dapat menemui jalan buntu. Sulit untuk menggambarkan coroutine tanpa bashing thread, jadi saya akan mengatakan coroutine adalah alternatif yang sangat elegan untuk threading.Generator secara efektif adalah fungsi yang mengembalikan (data) sebelum selesai, tetapi berhenti pada saat itu, dan Anda dapat melanjutkan fungsi pada saat itu.
dan seterusnya. Manfaat (atau satu) dari generator adalah karena mereka menangani data satu per satu, Anda dapat menangani sejumlah besar data; dengan daftar, kebutuhan memori yang berlebihan bisa menjadi masalah. Generator, seperti halnya daftar, dapat diubah, sehingga dapat digunakan dengan cara yang sama:
Perhatikan bahwa generator menyediakan cara lain untuk menangani ketidakterbatasan, misalnya
Generator merangkum loop tak terbatas, tetapi ini bukan masalah karena Anda hanya mendapatkan setiap jawaban setiap kali Anda memintanya.
sumber
Pertama-tama, istilah generator pada awalnya agak tidak jelas dalam Python, yang menyebabkan banyak kebingungan. Anda mungkin maksud iterator dan iterables (lihat di sini ). Kemudian dalam Python ada juga fungsi generator (yang mengembalikan objek generator), objek generator (yang merupakan iterator) dan ekspresi generator (yang dievaluasi ke objek generator).
Menurut entri glosarium untuk generator , sepertinya terminologi resmi sekarang adalah generator yang merupakan kependekan dari "fungsi generator". Di masa lalu dokumentasi mendefinisikan istilah tidak konsisten, tetapi untungnya ini telah diperbaiki.
Mungkin masih merupakan ide yang baik untuk menjadi tepat dan menghindari istilah "generator" tanpa spesifikasi lebih lanjut.
sumber
Generator dapat dianggap sebagai singkatan untuk membuat iterator. Mereka berperilaku seperti Java Iterator. Contoh:
Semoga ini bisa membantu / apa yang Anda cari.
Memperbarui:
Seperti banyak jawaban lain yang ditampilkan, ada berbagai cara untuk membuat generator. Anda dapat menggunakan sintaks kurung seperti dalam contoh saya di atas, atau Anda dapat menggunakan hasil. Fitur lain yang menarik adalah bahwa generator bisa "tak terbatas" - iterator yang tidak berhenti:
sumber
Stream
s, yang jauh lebih mirip dengan generator, kecuali bahwa Anda tampaknya tidak bisa mendapatkan elemen berikutnya tanpa kerumitan yang mengejutkan.Tidak ada padanan Java.
Ini sedikit contoh yang dibuat-buat:
Ada loop dalam generator yang berjalan dari 0 ke n, dan jika variabel loop adalah kelipatan 3, itu menghasilkan variabel.
Selama setiap iterasi dari
for
loop generator dijalankan. Jika ini adalah pertama kalinya generator dijalankan, itu dimulai di awal, jika tidak berlanjut dari waktu sebelumnya ia menghasilkan.sumber
print "hello"
setelahx=x+1
dalam contoh saya, "halo" akan dicetak 100 kali, sedangkan tubuh for for loop hanya akan dieksekusi 33 kali.Saya suka menggambarkan generator, untuk mereka yang memiliki latar belakang yang layak dalam bahasa pemrograman dan komputasi, dalam hal susunan bingkai.
Dalam banyak bahasa, ada tumpukan di atas yang merupakan tumpukan "bingkai" saat ini. Bingkai tumpukan termasuk ruang yang dialokasikan untuk variabel lokal ke fungsi termasuk argumen yang diteruskan ke fungsi itu.
Saat Anda memanggil fungsi, titik eksekusi saat ini ("penghitung program" atau yang setara) didorong ke tumpukan, dan bingkai tumpukan baru dibuat. Eksekusi kemudian mentransfer ke awal fungsi yang dipanggil.
Dengan fungsi reguler, pada suatu titik fungsi mengembalikan nilai, dan tumpukan "muncul". Bingkai tumpukan fungsi dibuang dan eksekusi dilanjutkan di lokasi sebelumnya.
Ketika suatu fungsi adalah generator, ia dapat mengembalikan nilai tanpa bingkai tumpukan yang dibuang, menggunakan pernyataan hasil. Nilai-nilai variabel lokal dan penghitung program dalam fungsi dipertahankan. Ini memungkinkan generator untuk dilanjutkan di lain waktu, dengan eksekusi berlanjut dari pernyataan hasil, dan dapat mengeksekusi lebih banyak kode dan mengembalikan nilai lain.
Sebelum Python 2.5 ini semua generator lakukan. Python 2,5 menambahkan kemampuan untuk melewati nilai kembali di generator juga. Dengan demikian, nilai yang lewat tersedia sebagai ekspresi yang dihasilkan dari pernyataan hasil yang telah mengembalikan kontrol (dan nilai) sementara dari generator.
Keuntungan utama untuk generator adalah bahwa "keadaan" fungsi dipertahankan, tidak seperti dengan fungsi biasa di mana setiap kali bingkai tumpukan dibuang, Anda kehilangan semua "keadaan" itu. Keuntungan sekunder adalah bahwa beberapa overhead panggilan fungsi (membuat dan menghapus bingkai tumpukan) dihindari, meskipun ini biasanya merupakan keuntungan kecil.
sumber
Satu-satunya hal yang dapat saya tambahkan ke jawaban Stephan202 adalah rekomendasi agar Anda melihat presentasi David Beazley's PyCon '08 "Trik Generator untuk Pemrogram Sistem," yang merupakan penjelasan tunggal terbaik tentang bagaimana dan mengapa generator yang pernah saya lihat dimana saja. Ini adalah hal yang membawa saya dari "Python kelihatan menyenangkan" menjadi "Ini yang saya cari." Ada di http://www.dabeaz.com/generators/ .
sumber
Ini membantu untuk membuat perbedaan yang jelas antara fungsi foo, dan generator foo (n):
foo adalah fungsi. foo (6) adalah objek generator.
Cara khas untuk menggunakan objek generator adalah dalam satu lingkaran:
Lingkaran dicetak
Pikirkan generator sebagai fungsi yang dapat dilanjutkan.
yield
berperilaku sepertireturn
dalam arti bahwa nilai-nilai yang dihasilkan dapat "dikembalikan" oleh generator. Tidak seperti return, bagaimanapun, pada saat generator ditanya nilai, fungsi generator, foo, melanjutkan di mana ia tinggalkan - setelah pernyataan hasil terakhir - dan terus berjalan sampai menyentuh pernyataan hasil yang lain.Di belakang layar, ketika Anda memanggil
bar=foo(6)
bilah objek generator ditentukan bagi Anda untuk memilikinext
atribut.Anda bisa menyebutnya sendiri untuk mengambil nilai yang dihasilkan dari foo:
Ketika foo berakhir (dan tidak ada lagi nilai yang dihasilkan), panggilan
next(bar)
melempar kesalahan StopInteration.sumber
Posting ini akan menggunakan angka Fibonacci sebagai alat untuk membangun guna menjelaskan kegunaan generator Python .
Posting ini akan menampilkan kode C ++ dan Python.
Angka-angka Fibonacci didefinisikan sebagai urutan: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
Atau secara umum:
Ini dapat ditransfer ke fungsi C ++ dengan sangat mudah:
Tetapi jika Anda ingin mencetak enam angka Fibonacci pertama, Anda akan menghitung ulang banyak nilai dengan fungsi di atas.
Misalnya :,
Fib(3) = Fib(2) + Fib(1)
tetapiFib(2)
juga menghitung ulangFib(1)
. Semakin tinggi nilai yang ingin Anda hitung, Anda akan semakin buruk.Jadi seseorang mungkin tergoda untuk menulis ulang di atas dengan melacak keadaan
main
.Tapi ini sangat jelek, dan mempersulit logika kita
main
. Akan lebih baik untuk tidak perlu khawatir tentang keadaan di kitamain
fungsi .Kita bisa mengembalikan a
vector
nilai dan menggunakan nilaiiterator
untuk mengulangi set nilai itu, tetapi ini membutuhkan banyak memori sekaligus untuk sejumlah besar nilai pengembalian.Jadi kembali ke pendekatan lama kita, apa yang terjadi jika kita ingin melakukan hal lain selain mencetak angka? Kami harus menyalin dan menempelkan seluruh blok kode
main
dan mengubah pernyataan keluaran menjadi apa pun yang ingin kami lakukan. Dan jika Anda menyalin dan menempelkan kode, maka Anda harus ditembak. Anda tidak ingin tertembak, bukan?Untuk mengatasi masalah ini, dan untuk menghindari tembakan, kami dapat menulis ulang blok kode ini menggunakan fungsi panggilan balik. Setiap kali nomor Fibonacci baru ditemukan, kami akan memanggil fungsi callback.
Ini jelas merupakan peningkatan, logika Anda
main
tidak berantakan, dan Anda dapat melakukan apa pun yang Anda inginkan dengan angka-angka Fibonacci, cukup tentukan panggilan balik baru.Tapi ini masih belum sempurna. Bagaimana jika Anda hanya ingin mendapatkan dua angka Fibonacci pertama, lalu melakukan sesuatu, lalu mendapatkan lebih banyak, lalu melakukan sesuatu yang lain?
Yah, kita bisa terus seperti dulu, dan kita bisa mulai menambahkan status lagi
main
, memungkinkan GetFibNumbers untuk memulai dari titik arbitrer. Tapi ini akan semakin menggembungkan kode kita, dan itu sudah terlihat terlalu besar untuk tugas sederhana seperti mencetak angka Fibonacci.Kita dapat menerapkan model produsen dan konsumen melalui beberapa utas. Tetapi ini semakin menyulitkan kode.
Sebagai gantinya mari kita bicara tentang generator.
Python memiliki fitur bahasa yang sangat bagus yang memecahkan masalah seperti ini yang disebut generator.
Generator memungkinkan Anda untuk mengeksekusi suatu fungsi, berhenti pada titik arbitrer, dan kemudian melanjutkan lagi di mana Anda tinggalkan. Setiap kali mengembalikan nilai.
Pertimbangkan kode berikut yang menggunakan generator:
Yang memberi kami hasil:
Itu
yield
pernyataan digunakan dalam conjuction dengan generator Python. Ini menyimpan status fungsi dan mengembalikan nilai yeilded. Lain kali Anda memanggil fungsi next () pada generator, ia akan melanjutkan di mana hasil ditinggalkan.Ini jauh lebih bersih daripada kode fungsi panggilan balik. Kami memiliki kode yang lebih bersih, kode yang lebih kecil, dan belum lagi kode yang lebih fungsional (Python memungkinkan bilangan bulat besar secara sewenang-wenang).
Sumber
sumber
Saya percaya penampilan pertama iterator dan generator dalam bahasa pemrograman Icon, sekitar 20 tahun yang lalu.
Anda dapat menikmati ikhtisar Ikon , yang memungkinkan Anda membungkusnya tanpa berkonsentrasi pada sintaks (karena Ikon adalah bahasa yang Anda mungkin tidak tahu, dan Griswold menjelaskan manfaat bahasanya kepada orang-orang yang datang dari bahasa lain).
Setelah membaca hanya beberapa paragraf di sana, utilitas generator dan iterator mungkin menjadi lebih jelas.
sumber
Pengalaman dengan daftar pemahaman telah menunjukkan utilitas luas mereka di seluruh Python. Namun, banyak kasus penggunaan tidak perlu memiliki daftar lengkap yang dibuat dalam memori. Sebaliknya, mereka hanya perlu mengulangi elemen satu per satu.
Sebagai contoh, kode penjumlahan berikut akan membangun daftar kuadrat penuh dalam memori, beralih pada nilai-nilai itu, dan, ketika referensi tidak lagi diperlukan, hapus daftar:
sum([x*x for x in range(10)])
Memori disimpan dengan menggunakan ekspresi generator sebagai gantinya:
sum(x*x for x in range(10))
Manfaat serupa diberikan pada konstruktor untuk objek kontainer:
Ekspresi generator sangat berguna dengan fungsi seperti jumlah (), min (), dan maks () yang mengurangi input yang dapat diulang ke nilai tunggal:
lebih
sumber
Saya memasang potongan kode ini yang menjelaskan 3 konsep utama tentang generator:
sumber