Untuk memahami apa yang yield
dilakukan, Anda harus memahami apa itu generator . Dan sebelum Anda dapat memahami generator, Anda harus memahami iterables .
Iterables
Saat Anda membuat daftar, Anda dapat membaca itemnya satu per satu. Membaca itemnya satu per satu disebut iterasi:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
adalah iterable . Ketika Anda menggunakan pemahaman daftar, Anda membuat daftar, dan iterable:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Segala sesuatu yang Anda dapat gunakan " for... in...
" pada adalah iterable; lists
,, strings
file ...
Iterables ini berguna karena Anda dapat membacanya sebanyak yang Anda inginkan, tetapi Anda menyimpan semua nilai dalam memori dan ini tidak selalu seperti yang Anda inginkan ketika Anda memiliki banyak nilai.
Generator
Generator adalah iterator, semacam iterable yang hanya bisa Anda ulangi sekali . Generator tidak menyimpan semua nilai dalam memori, mereka menghasilkan nilai dengan cepat :
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
Itu sama saja kecuali Anda menggunakan ()
bukan []
. TETAPI, Anda tidak dapat melakukan for i in mygenerator
kedua kalinya karena generator hanya dapat digunakan satu kali: mereka menghitung 0, lalu melupakannya dan menghitung 1, dan akhirnya menghitung 4, satu per satu.
Menghasilkan
yield
adalah kata kunci yang digunakan seperti return
, kecuali fungsi akan mengembalikan generator.
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
Ini adalah contoh yang tidak berguna, tetapi berguna ketika Anda tahu fungsi Anda akan mengembalikan sejumlah besar nilai yang hanya perlu Anda baca sekali.
Untuk menguasai yield
, Anda harus memahami bahwa ketika Anda memanggil fungsi, kode yang Anda tulis di badan fungsi tidak berjalan. Fungsi hanya mengembalikan objek generator, ini agak rumit :-)
Kemudian, kode Anda akan dilanjutkan dari tempat penghentiannya setiap kali for
menggunakan generator.
Sekarang bagian yang sulit:
Pertama kali for
memanggil objek generator yang dibuat dari fungsi Anda, itu akan menjalankan kode dalam fungsi Anda dari awal sampai hits yield
, maka itu akan mengembalikan nilai pertama dari loop. Kemudian, setiap panggilan berikutnya akan menjalankan iterasi lain dari loop yang telah Anda tulis dalam fungsi dan mengembalikan nilai berikutnya. Ini akan berlanjut sampai generator dianggap kosong, yang terjadi ketika fungsi berjalan tanpa memukul yield
. Itu bisa karena loop telah berakhir, atau karena Anda tidak lagi memuaskan "if/else"
.
Kode Anda dijelaskan
Generator:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if the distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if the distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
Penelepon:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidate's list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
Kode ini mengandung beberapa bagian cerdas:
Pengulangan berulang pada daftar, tetapi daftar mengembang saat pengulangan diulangi :-) Ini adalah cara ringkas untuk menelusuri semua data bersarang ini meskipun itu sedikit berbahaya karena Anda bisa berakhir dengan pengulangan tak terbatas. Dalam hal ini, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
buang semua nilai generator, tetapi while
terus membuat objek generator baru yang akan menghasilkan nilai yang berbeda dari yang sebelumnya karena tidak diterapkan pada node yang sama.
The extend()
Metode adalah metode daftar objek yang mengharapkan iterable dan menambahkan nilai-nilai ke dalam daftar.
Biasanya kami memberikan daftar itu:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Tetapi dalam kode Anda, ia mendapatkan generator, yang bagus karena:
- Anda tidak perlu membaca nilai dua kali.
- Anda mungkin memiliki banyak anak dan Anda tidak ingin mereka semua tersimpan dalam memori.
Dan itu berhasil karena Python tidak peduli jika argumen suatu metode adalah daftar atau tidak. Python mengharapkan iterables sehingga akan bekerja dengan string, daftar, tupel, dan generator! Ini disebut pengetikan bebek dan merupakan salah satu alasan mengapa Python sangat keren. Tapi ini cerita lain, untuk pertanyaan lain ...
Anda bisa berhenti di sini, atau membaca sedikit untuk melihat penggunaan generator tingkat lanjut:
Mengontrol kelelahan generator
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
Catatan: Untuk Python 3, gunakan print(corner_street_atm.__next__())
atauprint(next(corner_street_atm))
Ini dapat berguna untuk berbagai hal seperti mengendalikan akses ke sumber daya.
Itertools, sahabatmu
Modul itertools berisi fungsi-fungsi khusus untuk memanipulasi iterables. Pernah ingin menduplikasi generator? Rantai dua generator? Nilai grup dalam daftar bersarang dengan satu garis? Map / Zip
tanpa membuat daftar lain?
Lalu saja import itertools
.
Sebuah contoh? Mari kita lihat kemungkinan pesanan kedatangan untuk balap empat kuda:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
Memahami mekanisme internal iterasi
Iterasi adalah proses yang menyiratkan iterables (menerapkan __iter__()
metode) dan iterator (menerapkan __next__()
metode). Iterables adalah objek apa pun yang bisa Anda peroleh dengan iterator. Iterator adalah objek yang memungkinkan Anda beralih di iterables.
Ada lebih banyak tentang hal ini dalam artikel ini tentang cara for
kerja loop .
yield
tidak ajaib seperti yang dijawab oleh jawaban ini. Saat Anda memanggil fungsi yang berisiyield
pernyataan di mana saja, Anda mendapatkan objek generator, tetapi tidak ada kode yang berjalan. Kemudian setiap kali Anda mengekstrak objek dari generator, Python mengeksekusi kode dalam fungsi sampai datang keyield
pernyataan, lalu berhenti sebentar dan mengirimkan objek. Ketika Anda mengekstrak objek lain, Python melanjutkan setelahyield
dan berlanjut hingga mencapai yang lainyield
(sering kali sama, tetapi satu iterasi kemudian). Ini berlanjut sampai fungsi mati, di mana generator dianggap habis.()
bukan[]
, khususnya apa()
yang ada (mungkin ada kebingungan dengan tuple).return
pernyataan. (return
diizinkan dalam fungsi yang mengandungyield
, asalkan tidak menentukan nilai kembali.)Jalan pintas menuju pemahaman
yield
Saat Anda melihat fungsi dengan
yield
pernyataan, terapkan trik mudah ini untuk memahami apa yang akan terjadi:result = []
di awal fungsi.yield expr
- masing denganresult.append(expr)
.return result
di bagian bawah fungsi.yield
pernyataan! Baca dan temukan kode.Trik ini mungkin memberi Anda gagasan tentang logika di balik fungsi, tetapi apa yang sebenarnya terjadi dengan
yield
sangat berbeda dari apa yang terjadi dalam pendekatan berbasis daftar. Dalam banyak kasus, pendekatan hasil akan jauh lebih efisien dan lebih cepat. Dalam kasus lain, trik ini akan membuat Anda terjebak dalam infinite loop, meskipun fungsi aslinya berfungsi dengan baik. Baca terus untuk mengetahui lebih lanjut ...Jangan membingungkan Iterables, Iterators, dan Generator Anda
Pertama, protokol iterator - saat Anda menulis
Python melakukan dua langkah berikut:
Mendapat iterator untuk
mylist
:Panggil
iter(mylist)
-> ini mengembalikan objek dengannext()
metode (atau__next__()
dengan Python 3).[Ini adalah langkah yang kebanyakan orang lupa untuk memberi tahu Anda tentang]
Menggunakan iterator untuk mengulang item:
Terus memanggil
next()
metode pada iterator yang dikembalikan dari langkah 1. Nilai kembali darinext()
ditugaskan kex
dan tubuh loop dijalankan. Jika pengecualianStopIteration
dimunculkan dari dalamnext()
, itu berarti tidak ada lagi nilai di iterator dan loop keluar.Yang benar adalah Python melakukan dua langkah di atas kapan saja ia ingin mengulang isi suatu objek - jadi itu bisa untuk perulangan, tetapi bisa juga berupa kode
otherlist.extend(mylist)
(di manaotherlist
ada daftar Python).Berikut
mylist
ini adalah iterable karena mengimplementasikan protokol iterator. Di kelas yang ditentukan pengguna, Anda bisa mengimplementasikan__iter__()
metode untuk membuat instance kelas Anda dapat diubah. Metode ini harus mengembalikan iterator . Iterator adalah objek dengannext()
metode. Dimungkinkan untuk mengimplementasikan keduanya__iter__()
dannext()
di kelas yang sama, dan__iter__()
kembaliself
. Ini akan bekerja untuk kasus-kasus sederhana, tetapi tidak ketika Anda ingin dua iterator mengulangi objek yang sama secara bersamaan.Jadi itulah protokol iterator, banyak objek mengimplementasikan protokol ini:
__iter__()
.Perhatikan bahwa satu
for
loop tidak tahu objek apa yang dihadapinya - itu hanya mengikuti protokol iterator, dan dengan senang hati mendapatkan item setelah item yang dipanggilnext()
. Daftar built-in mengembalikan item mereka satu per satu, kamus mengembalikan kunci satu per satu, file mengembalikan baris satu per satu, dll. Dan generator kembali ... baik di situlahyield
masuk:Alih-alih
yield
pernyataan, jika Anda memiliki tigareturn
pernyataanf123()
hanya yang pertama akan dieksekusi, dan fungsi akan keluar. Tetapif123()
tidak ada fungsi biasa. Ketikaf123()
dipanggil, itu tidak mengembalikan nilai apa pun dalam laporan hasil! Ini mengembalikan objek generator. Juga, fungsi tidak benar-benar keluar - itu masuk ke status ditangguhkan. Ketikafor
loop mencoba untuk loop di atas objek generator, fungsi melanjutkan dari status ditangguhkan di baris berikutnya setelahyield
itu kembali dari sebelumnya, mengeksekusi baris kode berikutnya, dalam hal ini,yield
pernyataan, dan mengembalikannya sebagai berikutnya barang. Ini terjadi sampai fungsi keluar, pada titik mana generator menaikkanStopIteration
, dan loop keluar.Jadi objek generator seperti adaptor - di satu sisi ia memperlihatkan protokol iterator, dengan mengekspos
__iter__()
dannext()
metode untuk menjagafor
loop tetap bahagia. Di ujung lain, ia menjalankan fungsi cukup untuk mendapatkan nilai berikutnya darinya, dan mengembalikannya ke mode ditangguhkan.Mengapa Menggunakan Generator?
Biasanya, Anda dapat menulis kode yang tidak menggunakan generator tetapi mengimplementasikan logika yang sama. Salah satu pilihan adalah menggunakan 'trik' daftar sementara yang saya sebutkan sebelumnya. Itu tidak akan berfungsi dalam semua kasus, misalnya jika Anda memiliki loop tak terbatas, atau mungkin menggunakan memori yang tidak efisien ketika Anda memiliki daftar yang sangat panjang. Pendekatan lain adalah menerapkan kelas iterable baru SomethingIter yang membuat negara anggota tetap dan melakukan langkah logis berikutnya dalam metode itu
next()
(atau__next__()
dengan Python 3). Bergantung pada logika, kode di dalamnext()
metode ini mungkin terlihat sangat rumit dan rentan terhadap bug. Di sini generator memberikan solusi yang bersih dan mudah.sumber
send
menjadi generator, yang merupakan bagian besar dari titik generator?otherlist.extend(mylist)
" -> Ini tidak benar.extend()
memodifikasi daftar di tempat dan tidak mengembalikan iterable. Mencoba untuk mengulangotherlist.extend(mylist)
akan gagal dengan mengembalikanTypeError
karenaextend()
secara implisitNone
, dan Anda tidak dapat mengulangNone
.mylist
(tidak aktifotherlist
) ketika mengeksekusiotherlist.extend(mylist)
.Pikirkan seperti ini:
Sebuah iterator hanyalah istilah terdengar mewah untuk objek yang memiliki
next()
metode. Jadi fungsi hasil-ed akhirnya menjadi seperti ini:Versi asli:
Ini pada dasarnya adalah apa yang dilakukan penerjemah Python dengan kode di atas:
Untuk wawasan lebih lanjut tentang apa yang terjadi di balik layar,
for
loop dapat ditulis ulang untuk ini:Apakah itu lebih masuk akal atau hanya membuat Anda lebih bingung? :)
Saya harus mencatat bahwa ini adalah penyederhanaan yang berlebihan untuk tujuan ilustrasi. :)
sumber
__getitem__
dapat didefinisikan sebagai ganti__iter__
. Sebagai contoh:,class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)
Ini akan mencetak: 0, 10, 20, ..., 90iterator = some_function()
, variabeliterator
tidak memiliki fungsi yang disebutnext()
lagi, tetapi hanya sebuah__next__()
fungsi. Kupikir aku akan menyebutkannya.for
implementasi loop yang Anda tulis memanggil__iter__
metodeiterator
, instantiated instance ofit
?Kata
yield
kunci dikurangi menjadi dua fakta sederhana:yield
kata kunci di mana saja di dalam suatu fungsi, fungsi itu tidak lagi kembali melaluireturn
pernyataan. Sebagai gantinya , ia segera mengembalikan objek "pending list" yang malas yang disebut generatorlist
atauset
ataurange
atau tampilan dict, dengan protokol bawaan untuk mengunjungi setiap elemen dalam urutan tertentu .Singkatnya: generator adalah daftar malas, tambahan-tertunda , dan
yield
pernyataan memungkinkan Anda untuk menggunakan notasi fungsi untuk memprogram nilai - nilai daftar generator yang harus dimuntahkan secara bertahap.Contoh
Mari kita mendefinisikan fungsi
makeRange
yang persis seperti Pythonrange
. MemanggilmakeRange(n)
RETURNS GENERATOR:Untuk memaksa generator untuk segera mengembalikan nilai yang tertunda, Anda dapat meneruskannya
list()
(sama seperti Anda dapat melakukannya):Membandingkan contoh dengan "hanya mengembalikan daftar"
Contoh di atas dapat dianggap sebagai hanya membuat daftar yang Anda tambahkan dan kembalikan:
Namun, ada satu perbedaan besar; lihat bagian terakhir.
Bagaimana Anda bisa menggunakan generator
Iterable adalah bagian terakhir dari pemahaman daftar, dan semua generator bisa diubah, jadi mereka sering digunakan seperti:
Untuk mendapatkan rasa yang lebih baik untuk generator, Anda dapat bermain-main dengan
itertools
modul (pastikan untuk menggunakanchain.from_iterable
daripadachain
saat diperlukan). Sebagai contoh, Anda bahkan dapat menggunakan generator untuk mengimplementasikan daftar malas yang sangat panjang sepertiitertools.count()
. Anda dapat menerapkan sendiridef enumerate(iterable): zip(count(), iterable)
, atau melakukannya denganyield
kata kunci dalam loop sementara.Harap dicatat: generator sebenarnya dapat digunakan untuk banyak hal lagi, seperti menerapkan coroutine atau pemrograman non-deterministik atau hal-hal elegan lainnya. Namun, sudut pandang "daftar malas" yang saya sajikan di sini adalah penggunaan paling umum yang akan Anda temukan.
Di balik layar
Ini adalah cara kerja "protokol iterasi Python" bekerja. Yaitu, apa yang terjadi ketika Anda melakukannya
list(makeRange(5))
. Inilah yang saya gambarkan sebelumnya sebagai "daftar malas, tambahan".Fungsi built-in
next()
hanya memanggil.next()
fungsi objek , yang merupakan bagian dari "protokol iterasi" dan ditemukan di semua iterator. Anda dapat secara manual menggunakannext()
fungsi (dan bagian lain dari protokol iterasi) untuk mengimplementasikan hal-hal mewah, biasanya dengan mengorbankan keterbacaan, jadi cobalah untuk menghindari melakukan itu ...Detel
Biasanya, kebanyakan orang tidak akan peduli dengan perbedaan berikut dan mungkin ingin berhenti membaca di sini.
Dalam Python-speak, iterable adalah objek apa pun yang "memahami konsep for-loop" seperti daftar
[1,2,3]
, dan iterator adalah contoh spesifik dari yang diminta untuk-loop seperti[1,2,3].__iter__()
. Sebuah generator persis sama dengan iterator apa pun, kecuali cara itu ditulis (dengan sintaks fungsi).Ketika Anda meminta iterator dari daftar, itu membuat iterator baru. Namun, ketika Anda meminta iterator dari iterator (yang jarang Anda lakukan), itu hanya memberi Anda salinannya sendiri.
Jadi, jika Anda gagal melakukan sesuatu seperti ini ...
... maka ingatlah bahwa generator adalah iterator ; yaitu, ini hanya sekali pakai. Jika Anda ingin menggunakannya kembali, Anda harus menelepon
myRange(...)
lagi. Jika Anda perlu menggunakan hasilnya dua kali, konversikan hasilnya ke daftar dan simpan dalam variabelx = list(myRange(5))
. Mereka yang benar-benar perlu mengkloning generator (misalnya, yang melakukan metaprogramming yang sangat menakutkan) dapat menggunakannyaitertools.tee
jika benar-benar diperlukan, karena proposal standar Python PEP yang dapat disalin telah ditangguhkan.sumber
Jawab Garis Besar / Ringkasan
yield
, ketika dipanggil, mengembalikan Generator .yield from
.return
dalam generator.)Generator:
yield
hanya legal di dalam definisi fungsi, dan dimasukkannyayield
dalam definisi fungsi membuatnya mengembalikan generator.Gagasan untuk generator berasal dari bahasa lain (lihat catatan kaki 1) dengan beragam implementasi. Dalam Python Generator, eksekusi kode dibekukan pada titik hasil. Ketika generator dipanggil (metode dibahas di bawah) eksekusi akan dilanjutkan dan kemudian membeku di hasil berikutnya.
yield
menyediakan cara mudah untuk mengimplementasikan protokol iterator , yang didefinisikan oleh dua metode berikut:__iter__
dannext
(Python 2) atau__next__
(Python 3). Kedua metode tersebut membuat objek sebuah iterator yang bisa Anda ketik-periksa denganIterator
Abstrak Base Class daricollections
modul.Jenis generator adalah sub-jenis iterator:
Dan jika perlu, kita bisa mengetik-cek seperti ini:
Suatu fitur dari suatu
Iterator
yang sekali habis , Anda tidak dapat menggunakan kembali atau meresetnya:Anda harus membuat yang lain jika ingin menggunakan fungsinya lagi (lihat catatan kaki 2):
Seseorang dapat menghasilkan data secara terprogram, misalnya:
Generator sederhana di atas juga setara dengan di bawah ini - seperti Python 3.3 (dan tidak tersedia di Python 2), Anda dapat menggunakan
yield from
:Namun,
yield from
juga memungkinkan untuk didelegasikan kepada subgenerator, yang akan dijelaskan pada bagian berikut tentang delegasi koperasi dengan sub-coroutine.Coroutine:
yield
membentuk ekspresi yang memungkinkan data dikirim ke generator (lihat catatan kaki 3)Berikut ini sebuah contoh, perhatikan
received
variabel, yang akan menunjuk ke data yang dikirim ke generator:Pertama, kita harus membuat antrian generator dengan fungsi builtin
next
,. Ini akan memanggil metodenext
atau yang sesuai__next__
, tergantung pada versi Python yang Anda gunakan:Dan sekarang kita dapat mengirim data ke generator. ( Mengirim
None
sama dengan meneleponnext
.):Delegasi Koperasi untuk Sub-Coroutine dengan
yield from
Sekarang, ingatlah bahwa
yield from
tersedia dalam Python 3. Ini memungkinkan kita untuk mendelegasikan coroutine ke suboroutine:Dan sekarang kita dapat mendelegasikan fungsionalitas ke sub-generator dan dapat digunakan oleh generator seperti di atas:
Anda dapat membaca lebih lanjut tentang semantik yang tepat
yield from
dalam PEP 380.Metode lain: tutup dan lempar
The
close
Metode menimbulkanGeneratorExit
pada titik eksekusi fungsi membeku. Ini juga akan dipanggil oleh__del__
sehingga Anda dapat menempatkan kode pembersihan di mana Anda menanganiGeneratorExit
:Anda juga bisa melempar pengecualian yang bisa ditangani di generator atau disebarkan kembali ke pengguna:
Kesimpulan
Saya yakin saya telah membahas semua aspek dari pertanyaan berikut:
Ternyata itu
yield
banyak artinya. Saya yakin saya bisa menambahkan contoh yang lebih teliti untuk ini. Jika Anda ingin lebih atau memiliki kritik yang membangun, beri tahu saya dengan berkomentar di bawah ini.Lampiran:
Kritik Atas / Jawaban yang Diterima **
__iter__
metode mengembalikan iterator . Sebuah iterator menyediakan metode.next
(Python 2 atau.__next__
(Python 3), yang secara implisit disebut denganfor
loop sampai memunculkanStopIteration
, dan begitu berhasil, ia akan terus melakukannya.yield
bagian itu..next
metode, ketika sebaliknya ia harus menggunakan fungsi builtinnext
,. Ini akan menjadi lapisan tipuan yang tepat, karena kodenya tidak berfungsi di Python 3.yield
terjadi.yield
menyediakan bersama dengan fungsionalitas baruyield from
di Python 3. Jawaban teratas / diterima adalah jawaban yang sangat tidak lengkap.Kritik jawaban yang disarankan
yield
dalam ekspresi atau pemahaman generator.Tata bahasanya saat ini memungkinkan setiap ekspresi dalam pemahaman daftar.
Karena hasil adalah ekspresi, itu telah disebut-sebut oleh beberapa orang sebagai menarik untuk menggunakannya dalam pemahaman atau ekspresi generator - meskipun mengutip tidak ada kasus penggunaan yang sangat baik.
Pengembang inti CPython sedang mendiskusikan penghentian pemberiannya . Berikut pos yang relevan dari milis:
Lebih jauh lagi, ada masalah luar biasa (10544) yang tampaknya menunjuk ke arah ini tidak pernah menjadi ide yang baik (PyPy, implementasi Python yang ditulis dengan Python, sudah menaikkan peringatan sintaksis.)
Intinya, sampai pengembang CPython memberi tahu kami sebaliknya: Jangan menaruh
yield
ekspresi atau pemahaman generator.The
return
pernyataan dalam sebuah generatorDengan Python 2 :
An
expression_list
pada dasarnya adalah sejumlah ekspresi yang dipisahkan oleh koma - pada dasarnya, dengan Python 2, Anda dapat menghentikan generatorreturn
, tetapi Anda tidak dapat mengembalikan nilai.Dengan Python 3 :
Catatan kaki
Bahasa CLU, Sather, dan Icon dirujuk dalam proposal untuk memperkenalkan konsep generator ke Python. Gagasan umum adalah bahwa suatu fungsi dapat mempertahankan keadaan internal dan menghasilkan titik data menengah pada permintaan oleh pengguna. Ini berjanji akan unggul dalam kinerja dibandingkan pendekatan lain, termasuk Python threading , yang bahkan tidak tersedia pada beberapa sistem.
Ini berarti, misalnya, bahwa
xrange
objek (range
dalam Python 3) tidakIterator
s, meskipun mereka dapat diubah, karena mereka dapat digunakan kembali. Seperti daftar,__iter__
metode mereka mengembalikan objek iterator.yield
pada awalnya diperkenalkan sebagai pernyataan, yang berarti bahwa itu hanya bisa muncul di awal baris dalam blok kode. Sekarangyield
buat ekspresi hasil. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Perubahan ini diusulkan untuk memungkinkan pengguna mengirim data ke generator sama seperti orang mungkin menerimanya. Untuk mengirim data, seseorang harus dapat menetapkannya untuk sesuatu, dan untuk itu, pernyataan tidak akan berfungsi.sumber
yield
sama sepertireturn
- mengembalikan apa pun yang Anda katakan (sebagai generator). Perbedaannya adalah bahwa lain kali Anda memanggil generator, eksekusi dimulai dari panggilan terakhir keyield
pernyataan. Tidak seperti pengembalian, bingkai tumpukan tidak dibersihkan ketika hasil terjadi, namun kontrol ditransfer kembali ke pemanggil, sehingga statusnya akan dilanjutkan saat fungsi berikutnya dipanggil.Dalam kasus kode Anda, fungsinya
get_child_candidates
berfungsi seperti iterator sehingga ketika Anda memperluas daftar Anda, itu menambahkan satu elemen pada satu waktu ke daftar baru.list.extend
memanggil iterator sampai habis. Dalam hal contoh kode yang Anda posting, akan jauh lebih jelas untuk hanya mengembalikan tuple dan menambahkannya ke daftar.sumber
Ada satu hal tambahan untuk disebutkan: fungsi yang menghasilkan sebenarnya tidak harus berakhir. Saya sudah menulis kode seperti ini:
Maka saya dapat menggunakannya dalam kode lain seperti ini:
Ini benar-benar membantu menyederhanakan beberapa masalah, dan membuat beberapa hal lebih mudah untuk dikerjakan.
sumber
Bagi mereka yang lebih suka contoh kerja minimal, renungkan sesi Python interaktif ini:
sumber
TL; DR
Alih-alih ini:
melakukan hal ini:
Setiap kali Anda menemukan diri Anda membuat daftar dari awal,
yield
masing-masing bagian sebagai gantinya.Ini adalah momen "aha" pertamaku dengan hasil.
yield
adalah cara yang manis untuk mengatakanPerilaku yang sama:
Perilaku yang berbeda:
Hasil adalah single-pass : Anda hanya dapat mengulanginya sekali saja. Ketika suatu fungsi memiliki hasil di dalamnya kita menyebutnya fungsi generator . Dan sebuah iterator adalah apa yang dikembalikan. Istilah-istilah itu mengungkapkan. Kami kehilangan kenyamanan sebuah wadah, tetapi mendapatkan kekuatan seri yang dihitung sesuai kebutuhan, dan panjang sewenang-wenang.
Menghasilkan malas , itu menunda perhitungan. Fungsi dengan hasil di dalamnya tidak benar-benar mengeksekusi sama sekali ketika Anda menyebutnya. Ini mengembalikan objek iterator yang mengingat di mana ia tinggalkan. Setiap kali Anda memanggil
next()
iterator (ini terjadi dalam for-loop) eksekusi inci ke depan untuk hasil berikutnya.return
memunculkan StopIteration dan mengakhiri seri (ini adalah akhir alami for-loop).Hasil panen serba guna . Data tidak harus disimpan bersama-sama, data dapat dibuat satu per satu. Itu bisa tanpa batas.
Jika Anda membutuhkan beberapa lintasan dan seri ini tidak terlalu panjang, panggil
list()
saja:Pilihan kata yang brilian
yield
karena kedua arti berlaku:... berikan data berikutnya dalam seri.
... lepaskan eksekusi CPU hingga iterator bergerak maju.
sumber
Yield memberi Anda generator.
Seperti yang Anda lihat, dalam kasus pertama
foo
menyimpan seluruh daftar di memori sekaligus. Ini bukan masalah besar untuk daftar dengan 5 elemen, tetapi bagaimana jika Anda ingin daftar 5 juta? Tidak hanya pemakan memori yang besar ini, juga membutuhkan banyak waktu untuk membangun pada saat fungsi dipanggil.Dalam kasus kedua,
bar
hanya memberi Anda generator. Generator adalah iterable - yang berarti Anda dapat menggunakannya dalam satufor
lingkaran, dll, tetapi setiap nilai hanya dapat diakses satu kali. Semua nilai juga tidak disimpan dalam memori pada saat yang sama; objek generator "mengingat" di mana ia berada dalam perulangan saat terakhir Anda menyebutnya - dengan cara ini, jika Anda menggunakan iterable untuk (katakanlah) menghitung hingga 50 miliar, Anda tidak perlu menghitung hingga 50 miliar semua sekaligus dan simpan 50 miliar angka untuk dihitung.Sekali lagi, ini adalah contoh yang cukup dibuat-buat, Anda mungkin akan menggunakan itertools jika Anda benar-benar ingin menghitung hingga 50 miliar. :)
Ini adalah kasus penggunaan generator yang paling sederhana. Seperti yang Anda katakan, ini dapat digunakan untuk menulis permutasi yang efisien, menggunakan hasil untuk mendorong segalanya melalui tumpukan panggilan alih-alih menggunakan semacam variabel tumpukan. Generator juga dapat digunakan untuk melintasi pohon khusus, dan segala hal lainnya.
sumber
range
juga mengembalikan generator bukan daftar, jadi Anda juga akan melihat ide yang sama, kecuali bahwa__repr__
/ saya__str__
ditimpa untuk menunjukkan hasil yang lebih baik, dalam hal inirange(1, 10, 2)
.Ini mengembalikan generator. Saya tidak terlalu terbiasa dengan Python, tapi saya percaya itu adalah hal yang sama seperti blok iterator C # jika Anda terbiasa dengan itu.
Gagasan utamanya adalah bahwa kompiler / juru bahasa / apa pun melakukan beberapa tipu daya sehingga sejauh pemanggil yang bersangkutan, mereka dapat terus memanggil berikutnya () dan itu akan terus mengembalikan nilai - seolah-olah metode generator dijeda . Sekarang jelas Anda tidak dapat benar-benar "menghentikan" suatu metode, jadi kompiler membuat mesin keadaan agar Anda dapat mengingat di mana Anda saat ini dan seperti apa variabel lokal dll. Ini jauh lebih mudah daripada menulis iterator sendiri.
sumber
Ada satu jenis jawaban yang menurut saya belum diberikan, di antara banyak jawaban bagus yang menjelaskan cara menggunakan generator. Inilah jawaban teori bahasa pemrograman:
The
yield
pernyataan dalam Python mengembalikan generator. Generator dengan Python adalah fungsi yang mengembalikan kelanjutan (dan khususnya jenis coroutine, tetapi kelanjutan mewakili mekanisme yang lebih umum untuk memahami apa yang sedang terjadi).Kelanjutan dalam teori bahasa pemrograman adalah jenis perhitungan yang jauh lebih mendasar, tetapi mereka tidak sering digunakan, karena mereka sangat sulit untuk dipikirkan dan juga sangat sulit untuk diterapkan. Tetapi gagasan tentang apa yang merupakan kelanjutan, sangat mudah: itu adalah keadaan perhitungan yang belum selesai. Dalam keadaan ini, nilai variabel saat ini, operasi yang belum dilakukan, dan seterusnya, disimpan. Kemudian pada suatu saat nanti dalam program kelanjutan dapat dipanggil, sehingga variabel program diatur ulang ke keadaan itu dan operasi yang disimpan dilakukan.
Lanjutan, dalam bentuk yang lebih umum ini, dapat diimplementasikan dengan dua cara. Di
call/cc
jalan, tumpukan program secara harfiah disimpan dan kemudian ketika kelanjutan dipanggil, tumpukan dikembalikan.Dalam gaya kelanjutan kelanjutan (CPS), kelanjutan hanyalah fungsi normal (hanya dalam bahasa di mana fungsi adalah kelas satu) yang dikelola oleh programmer secara eksplisit dan diteruskan ke subrutin. Dalam gaya ini, keadaan program diwakili oleh penutupan (dan variabel yang dikodekan di dalamnya) daripada variabel yang berada di suatu tempat di tumpukan. Fungsi yang mengelola aliran kontrol menerima kelanjutan sebagai argumen (dalam beberapa variasi CPS, fungsi dapat menerima beberapa kelanjutan) dan memanipulasi aliran kontrol dengan memanggilnya hanya dengan memanggilnya dan kembali sesudahnya. Contoh gaya kelanjutan passing yang sangat sederhana adalah sebagai berikut:
Dalam contoh (sangat sederhana) ini, programmer menyimpan operasi untuk benar-benar menulis file ke dalam kelanjutan (yang berpotensi menjadi operasi yang sangat kompleks dengan banyak detail untuk dituliskan), dan kemudian meneruskan kelanjutan itu (yaitu, sebagai yang pertama- penutupan kelas) ke operator lain yang melakukan beberapa pemrosesan lebih lanjut, dan kemudian memanggilnya jika perlu. (Saya banyak menggunakan pola desain ini dalam pemrograman GUI yang sebenarnya, baik karena itu menyelamatkan saya baris kode atau, yang lebih penting, untuk mengelola aliran kontrol setelah peristiwa GUI memicu.)
Sisa dari posting ini akan, tanpa kehilangan keumuman, mengkonseptualisasikan kelanjutan sebagai CPS, karena itu jauh lebih mudah untuk dipahami dan dibaca.
Sekarang mari kita bicara tentang generator dengan Python. Generator adalah subtipe kelanjutan spesifik. Sementara kelanjutan secara umum dapat menyimpan keadaan perhitungan (yaitu, tumpukan panggilan program), generator hanya dapat menyimpan keadaan iterasi melalui iterator . Meskipun, definisi ini sedikit menyesatkan untuk kasus penggunaan generator tertentu. Contohnya:
Ini jelas merupakan iterable yang beralasan yang perilakunya didefinisikan dengan baik - setiap kali generator mengulanginya, ia mengembalikan 4 (dan melakukannya selamanya). Tapi itu mungkin bukan tipe prototipikal dari iterable yang muncul di pikiran ketika memikirkan iterator (yaitu,
for x in collection: do_something(x)
). Contoh ini menggambarkan kekuatan generator: jika ada sesuatu yang iterator, generator dapat menyelamatkan keadaan iterasi-nya.Untuk mengulangi: Lanjutan dapat menyimpan keadaan tumpukan program dan generator dapat menyimpan keadaan iterasi. Ini berarti bahwa kelanjutan lebih kuat daripada generator, tetapi juga bahwa generator jauh lebih mudah. Mereka lebih mudah untuk diterapkan oleh perancang bahasa, dan mereka lebih mudah bagi programmer untuk menggunakan (jika Anda punya waktu untuk membakar, cobalah membaca dan memahami halaman ini tentang kelanjutan dan panggilan / cc ).
Tetapi Anda dapat dengan mudah mengimplementasikan (dan membuat konsep) generator sebagai kasus sederhana, gaya kelanjutan lewat yang spesifik:
Setiap kali
yield
dipanggil, ia memberi tahu fungsi untuk mengembalikan kelanjutan. Ketika fungsi dipanggil lagi, itu dimulai dari mana saja ia tinggalkan. Jadi, dalam pseudo-pseudocode (yaitu, bukan pseudocode, tetapi bukan kode) metode generatornext
pada dasarnya adalah sebagai berikut:di mana
yield
kata kunci sebenarnya gula sintaksis untuk fungsi generator yang sebenarnya, pada dasarnya sesuatu seperti:Ingat bahwa ini hanya pseudocode dan implementasi sebenarnya dari generator di Python lebih kompleks. Tetapi sebagai latihan untuk memahami apa yang sedang terjadi, cobalah untuk menggunakan gaya kelanjutan meneruskan untuk mengimplementasikan objek generator tanpa menggunakan
yield
kata kunci.sumber
Berikut adalah contoh dalam bahasa sederhana. Saya akan memberikan korespondensi antara konsep manusia tingkat tinggi dengan konsep Python tingkat rendah.
Saya ingin beroperasi pada urutan angka, tetapi saya tidak ingin mengganggu diri saya dengan penciptaan urutan itu, saya hanya ingin fokus pada operasi yang ingin saya lakukan. Jadi, saya melakukan hal berikut:
Langkah ini sesuai dengan
def
ining fungsi generator, yaitu fungsi yang mengandung ayield
.Langkah ini sesuai dengan memanggil fungsi generator yang mengembalikan objek generator. Perhatikan bahwa Anda belum memberi tahu saya nomor apa pun; Anda hanya mengambil kertas dan pensil Anda.
Langkah ini sesuai dengan memanggil
.next()
objek generator.Langkah ini sesuai dengan objek generator yang mengakhiri tugasnya, dan menaikkan
StopIteration
pengecualian Fungsi generator tidak perlu menaikkan pengecualian. Ini dinaikkan secara otomatis ketika fungsi berakhir atau mengeluarkan areturn
.Inilah yang generator lakukan (fungsi yang berisi a
yield
); itu mulai mengeksekusi, berhenti setiap kali ia melakukanyield
, dan ketika diminta untuk.next()
nilai itu berlanjut dari titik itu yang terakhir. Ini sangat cocok dengan desain dengan protokol iterator Python, yang menjelaskan bagaimana cara meminta nilai secara berurutan.Pengguna protokol iterator yang paling terkenal adalah
for
perintah dengan Python. Jadi, setiap kali Anda melakukan:tidak masalah apakah
sequence
daftar, string, kamus atau objek generator seperti dijelaskan di atas; hasilnya sama: Anda membaca item dari urutan satu per satu.Perhatikan bahwa
def
fungsi ining yang berisiyield
kata kunci bukan satu-satunya cara untuk membuat generator; itu hanya cara termudah untuk membuatnya.Untuk informasi yang lebih akurat, baca tentang jenis iterator , pernyataan hasil dan generator dalam dokumentasi Python.
sumber
Sementara banyak jawaban menunjukkan mengapa Anda akan menggunakan
yield
untuk membuat generator, ada lebih banyak kegunaan untukyield
. Sangat mudah untuk membuat coroutine, yang memungkinkan berlalunya informasi antara dua blok kode. Saya tidak akan mengulangi salah satu contoh bagus yang telah diberikan tentang penggunaanyield
untuk membuat generator.Untuk membantu memahami apa yang
yield
dilakukan dalam kode berikut, Anda dapat menggunakan jari Anda untuk melacak siklus melalui kode apa pun yang memilikiyield
. Setiap kali jari Anda menyentuhyield
, Anda harus menunggu untuk anext
atau asend
dimasukkan. Ketika anext
dipanggil, Anda menelusuri kode sampai Anda menekanyield
... kode di sebelah kananyield
dievaluasi dan dikembalikan ke pemanggil ... lalu Anda menunggu. Ketikanext
dipanggil lagi, Anda melakukan loop lain melalui kode. Namun, Anda akan mencatat bahwa di coroutine,yield
juga dapat digunakan dengansend
... yang akan mengirim nilai dari pemanggil ke fungsi menghasilkan. Jika sebuahsend
diberikan, makayield
menerima nilai yang dikirim, dan meludahkannya di sisi kiri ... kemudian penelusuran melalui kode berlanjut hingga Anda menekanyield
lagi (mengembalikan nilai di akhir, seolah-olahnext
dipanggil).Sebagai contoh:
sumber
Ada
yield
kegunaan dan makna lain (sejak Python 3.3):Dari PEP 380 - Sintaks untuk Mendelegasikan ke Subgenerator :
Selain itu ini akan memperkenalkan (sejak Python 3.5):
untuk menghindari coroutine dikacaukan dengan generator biasa (hari
yield
ini digunakan di keduanya).sumber
Semua jawaban bagus, namun agak sulit bagi pemula.
Saya berasumsi Anda telah mempelajari
return
pernyataan itu.Sebagai analogi,
return
danyield
kembar.return
berarti 'kembali dan berhenti' sedangkan 'menghasilkan` berarti' kembali, tetapi lanjutkan 'Menjalankannya:
Lihat, Anda hanya mendapatkan satu nomor daripada daftar mereka.
return
tidak pernah membiarkan Anda menang dengan bahagia, hanya menerapkan sekali dan berhenti.Ganti
return
denganyield
:Sekarang, Anda menang untuk mendapatkan semua angka.
Membandingkan
return
yang berjalan sekali dan berhenti,yield
berjalan kali yang Anda rencanakan. Anda dapat mengartikanreturn
sebagaireturn one of them
, danyield
sebagaireturn all of them
. Ini disebutiterable
.Itu inti tentang
yield
.Perbedaan antara
return
output daftar dan objekyield
output adalah:Anda akan selalu mendapatkan [0, 1, 2] dari objek daftar tetapi hanya bisa mengambilnya dari '
yield
output objek ' sekali. Jadi, ia memilikigenerator
objek nama baru seperti yang ditampilkan diOut[11]: <generator object num_list at 0x10327c990>
.Kesimpulannya, sebagai metafora untuk grok itu:
return
danyield
kembarlist
dangenerator
kembarsumber
yield
. Ini penting, saya pikir, dan harus diungkapkan.Berikut adalah beberapa contoh Python tentang bagaimana sebenarnya mengimplementasikan generator seolah-olah Python tidak memberikan gula sintaksis untuk mereka:
Sebagai generator Python:
Menggunakan penutupan leksikal bukan generator
Menggunakan penutupan objek alih-alih generator (karena ClosuresAndObjectsAreEquivalent )
sumber
Saya akan memposting "baca halaman 19 dari 'Python: Essential Reference' Beazley untuk deskripsi singkat generator", tetapi sudah banyak yang memposting deskripsi yang baik.
Juga, perhatikan bahwa
yield
dapat digunakan dalam coroutine sebagai dual dari penggunaannya dalam fungsi generator. Meskipun tidak sama dengan penggunaan potongan kode Anda,(yield)
dapat digunakan sebagai ekspresi dalam suatu fungsi. Ketika seorang pemanggil mengirim nilai ke metode menggunakansend()
metode, maka coroutine akan mengeksekusi sampai(yield)
pernyataan berikutnya ditemui.Generator dan coroutine adalah cara yang keren untuk mengatur aplikasi tipe aliran data. Saya pikir akan bermanfaat mengetahui tentang penggunaan lain dari
yield
pernyataan dalam fungsi.sumber
Dari sudut pandang pemrograman, iterator diimplementasikan sebagai thunks .
Untuk mengimplementasikan iterator, generator, dan kumpulan utas untuk eksekusi bersamaan, dll. Sebagai thunks (juga disebut fungsi anonim), seseorang menggunakan pesan yang dikirim ke objek penutupan, yang memiliki dispatcher, dan dispatcher menjawab "pesan".
http://en.wikipedia.org/wiki/Message_passing
" selanjutnya " adalah pesan yang dikirim ke penutupan, dibuat oleh panggilan " iter ".
Ada banyak cara untuk mengimplementasikan perhitungan ini. Saya menggunakan mutasi, tetapi mudah melakukannya tanpa mutasi, dengan mengembalikan nilai saat ini dan yielder berikutnya.
Ini adalah demonstrasi yang menggunakan struktur R6RS, tetapi semantiknya benar-benar identik dengan Python. Ini adalah model perhitungan yang sama, dan hanya perubahan sintaks yang diperlukan untuk menulis ulang dengan Python.
sumber
Ini adalah contoh sederhana:
Keluaran:
Saya bukan pengembang Python, tetapi sepertinya bagi saya
yield
memegang posisi aliran program dan loop berikutnya mulai dari posisi "hasil". Sepertinya sedang menunggu di posisi itu, dan tepat sebelum itu, mengembalikan nilai di luar, dan waktu berikutnya terus bekerja.Tampaknya menjadi kemampuan yang menarik dan menyenangkan: D
sumber
Inilah gambaran mental tentang apa yang
yield
dilakukannya.Saya suka menganggap utas sebagai memiliki tumpukan (bahkan ketika itu tidak diterapkan seperti itu).
Ketika fungsi normal dipanggil, ia menempatkan variabel lokalnya di stack, melakukan beberapa perhitungan, kemudian membersihkan stack dan kembali. Nilai-nilai variabel lokalnya tidak pernah terlihat lagi.
Dengan suatu
yield
fungsi, ketika kodenya mulai berjalan (yaitu setelah fungsi dipanggil, mengembalikan objek generator, yangnext()
metodenya kemudian dipanggil), ia juga menempatkan variabel lokalnya ke stack dan menghitung untuk sementara waktu. Tetapi kemudian, ketika ia mencapaiyield
pernyataan, sebelum membersihkan bagian dari tumpukan dan kembali, ia mengambil snapshot variabel lokalnya dan menyimpannya dalam objek generator. Itu juga menuliskan tempat di mana ia saat ini dalam kode (yaituyield
pernyataan tertentu ).Jadi itu semacam fungsi beku yang tergantung pada generator.
Ketika
next()
dipanggil selanjutnya, ia mengambil barang-barang fungsi ke stack dan menghidupkan kembali animasinya. Fungsi terus menghitung dari tempat itu pergi, tidak menyadari fakta bahwa ia baru saja menghabiskan keabadian dalam penyimpanan dingin.Bandingkan contoh-contoh berikut:
Ketika kita memanggil fungsi kedua, ia berperilaku sangat berbeda dengan yang pertama. The
yield
Pernyataan mungkin tidak terjangkau, tetapi jika itu di mana saja hadir, perubahan sifat dari apa yang kita hadapi.Memanggil
yielderFunction()
tidak menjalankan kode, tetapi membuat generator keluar dari kode. (Mungkin ide yang baik untuk menyebutkan hal-hal seperti itu denganyielder
awalan agar mudah dibaca.)The
gi_code
dangi_frame
bidang yang mana keadaan beku disimpan. Menjelajahi mereka dengandir(..)
, kita dapat mengkonfirmasi bahwa model mental kita di atas dapat dipercaya.sumber
Seperti setiap jawaban yang disarankan,
yield
digunakan untuk membuat generator urutan. Ini digunakan untuk menghasilkan beberapa urutan secara dinamis. Misalnya, saat membaca file baris demi baris di jaringan, Anda dapat menggunakanyield
fungsi sebagai berikut:Anda dapat menggunakannya dalam kode Anda sebagai berikut:
Eksekusi Kontrol, transfer gotcha
Kontrol eksekusi akan ditransfer dari getNextLines () ke
for
loop ketika hasil dijalankan. Jadi, setiap kali getNextLines () dipanggil, eksekusi dimulai dari titik di mana ia dijeda terakhir kali.Jadi singkatnya, fungsi dengan kode berikut
akan dicetak
sumber
Contoh mudah untuk memahami apa itu:
yield
Outputnya adalah:
sumber
print(i, end=' ')
? Kalau tidak, saya percaya perilaku default akan menempatkan setiap nomor pada baris baru(Jawaban saya di bawah ini hanya berbicara dari perspektif menggunakan generator Python, bukan implementasi mekanisme generator yang mendasarinya , yang melibatkan beberapa trik stack dan manipulasi heap.)
Ketika
yield
digunakan alih-alihreturn
dalam fungsi python, fungsi itu diubah menjadi sesuatu yang disebut khususgenerator function
. Fungsi itu akan mengembalikan objekgenerator
bertipe. Katayield
kunci adalah bendera untuk memberi tahu kompiler python untuk memperlakukan fungsi tersebut secara khusus. Fungsi normal akan berakhir setelah beberapa nilai dikembalikan darinya. Tetapi dengan bantuan kompiler, fungsi generator dapat dianggap sebagai resume. Artinya, konteks eksekusi akan dipulihkan dan eksekusi akan berlanjut dari yang terakhir kali dijalankan. Sampai Anda secara eksplisit memanggil kembali, yang akan memunculkanStopIteration
pengecualian (yang juga merupakan bagian dari protokol iterator), atau mencapai akhir fungsi. Saya menemukan banyak referensi tentanggenerator
tetapi ini satudarifunctional programming perspective
adalah yang paling mudah dicerna.(Sekarang saya ingin berbicara tentang alasan di balik
generator
, daniterator
berdasarkan pada pemahaman saya sendiri. Saya harap ini dapat membantu Anda memahami motivasi penting dari iterator dan generator. Konsep tersebut muncul dalam bahasa lain juga seperti C #.)Seperti yang saya mengerti, ketika kita ingin memproses banyak data, kita biasanya menyimpan data di suatu tempat dan kemudian memprosesnya satu per satu. Tetapi pendekatan naif ini bermasalah. Jika volume data sangat besar, sangat mahal untuk menyimpannya secara keseluruhan sebelumnya. Jadi alih-alih menyimpan
data
sendiri secara langsung, mengapa tidak menyimpan semacammetadata
tidak langsung, yaituthe logic how the data is computed
.Ada 2 pendekatan untuk membungkus metadata tersebut.
as a class
. Inilah yang disebutiterator
yang mengimplementasikan protokol iterator (yaitu__next__()
, dan__iter__()
metode). Ini juga merupakan pola desain iterator yang biasa dilihat .as a function
. Inilah yang disebutgenerator function
. Tetapi di bawah kap, yang dikembalikangenerator object
masihIS-A
iterator karena juga mengimplementasikan protokol iterator.Either way, sebuah iterator dibuat, yaitu beberapa objek yang dapat memberi Anda data yang Anda inginkan. Pendekatan OO mungkin agak rumit. Pokoknya, mana yang harus digunakan terserah Anda.
sumber
Singkatnya,
yield
pernyataan mengubah fungsi Anda menjadi sebuah pabrik yang menghasilkan objek khusus yang disebutgenerator
membungkus di sekitar tubuh fungsi asli Anda. Ketikagenerator
iterasi, itu mengeksekusi fungsi Anda sampai mencapai berikutnyayield
kemudian menunda eksekusi dan mengevaluasi nilai yang diteruskanyield
. Itu mengulangi proses ini pada setiap iterasi sampai jalan eksekusi keluar dari fungsi. Contohnya,hanya keluaran
Kekuatan berasal dari menggunakan generator dengan loop yang menghitung urutan, generator mengeksekusi loop berhenti setiap kali untuk 'menghasilkan' hasil perhitungan berikutnya, dengan cara ini menghitung daftar dengan cepat, manfaatnya adalah memori disimpan untuk perhitungan yang sangat besar
Katakanlah Anda ingin membuat
range
fungsi Anda sendiri yang menghasilkan rentang angka yang dapat diubah, Anda bisa melakukannya seperti itu,dan gunakan seperti ini;
Tetapi ini tidak efisien karena
Untungnya Guido dan timnya cukup dermawan untuk mengembangkan generator sehingga kami bisa melakukan ini;
Sekarang pada setiap iterasi fungsi pada generator yang disebut
next()
menjalankan fungsi sampai ia mencapai pernyataan 'hasil' di mana ia berhenti dan 'menghasilkan' nilai atau mencapai akhir fungsi. Dalam hal ini pada panggilan pertama,next()
jalankan hingga pernyataan hasil dan hasil 'n', pada panggilan berikutnya akan mengeksekusi pernyataan kenaikan, melompat kembali ke 'sementara', mengevaluasi itu, dan jika benar, itu akan berhenti dan menghasilkan 'n' lagi, itu akan terus seperti itu sampai kondisi sementara mengembalikan false dan generator melompat ke ujung fungsi.sumber
Hasil adalah suatu objek
A
return
dalam suatu fungsi akan mengembalikan nilai tunggal.Jika Anda ingin fungsi mengembalikan set nilai yang besar , gunakan
yield
.Lebih penting lagi,
yield
adalah penghalang .Artinya, itu akan menjalankan kode dalam fungsi Anda dari awal hingga hits
yield
. Kemudian, itu akan mengembalikan nilai pertama dari loop.Kemudian, setiap panggilan lain akan menjalankan loop yang telah Anda tulis dalam fungsi sekali lagi, mengembalikan nilai berikutnya sampai tidak ada nilai untuk kembali.
sumber
Banyak orang menggunakan
return
daripadayield
, tetapi dalam beberapa kasusyield
bisa lebih efisien dan lebih mudah untuk dikerjakan.Berikut adalah contoh yang
yield
pasti terbaik untuk:Kedua fungsi melakukan hal yang sama, tetapi
yield
menggunakan tiga baris alih-alih lima dan memiliki satu variabel yang kurang perlu dikhawatirkan.Seperti yang Anda lihat, kedua fungsi melakukan hal yang sama. Satu-satunya perbedaan adalah
return_dates()
memberikan daftar danyield_dates()
memberikan generator.Contoh kehidupan nyata adalah sesuatu seperti membaca file baris demi baris atau jika Anda hanya ingin membuat generator.
sumber
yield
seperti elemen kembali untuk suatu fungsi. Perbedaannya adalah,yield
elemen mengubah fungsi menjadi generator. Generator berperilaku seperti fungsi sampai sesuatu 'dihasilkan'. Generator berhenti sampai dipanggil berikutnya, dan berlanjut dari titik yang persis sama dengan saat dimulai. Anda bisa mendapatkan urutan semua nilai 'yang dihasilkan' dalam satu, dengan meneleponlist(generator())
.sumber
Kata
yield
kunci hanya mengumpulkan hasil yang kembali. Pikirkanyield
sepertireturn +=
sumber
Berikut ini adalah
yield
pendekatan berbasis sederhana , untuk menghitung seri fibonacci, menjelaskan:Ketika Anda memasukkan ini ke dalam REPL Anda dan kemudian mencoba dan menyebutnya, Anda akan mendapatkan hasil membingungkan:
Ini karena adanya
yield
sinyal ke Python bahwa Anda ingin membuat generator , yaitu, objek yang menghasilkan nilai berdasarkan permintaan.Jadi, bagaimana Anda menghasilkan nilai-nilai ini? Ini dapat dilakukan secara langsung dengan menggunakan fungsi bawaan
next
, atau, secara tidak langsung dengan mengumpankannya ke konstruk yang mengonsumsi nilai.Menggunakan
next()
fungsi bawaan, Anda secara langsung memanggil.next
/__next__
, memaksa generator untuk menghasilkan nilai:Secara tidak langsung, jika Anda memberikan
fib
kefor
loop,list
penginisialisasi,tuple
penginisialisasi, atau apa pun yang mengharapkan objek yang menghasilkan / menghasilkan nilai, Anda akan "mengkonsumsi" generator sampai tidak ada lagi nilai yang dapat dihasilkan olehnya (dan mengembalikan) :Demikian pula dengan
tuple
penginisialisasi:Generator berbeda dari fungsi dalam arti malas. Ini menyelesaikan ini dengan mempertahankan keadaan lokal dan memungkinkan Anda untuk melanjutkan kapan pun Anda perlu.
Ketika Anda pertama kali memanggil
fib
dengan memanggilnya:Python mengkompilasi fungsi, menemukan
yield
kata kunci dan hanya mengembalikan objek generator kembali kepada Anda. Sepertinya tidak terlalu membantu.Ketika Anda kemudian meminta itu menghasilkan nilai pertama, langsung atau tidak langsung, itu mengeksekusi semua pernyataan yang ditemukannya, sampai bertemu a
yield
, itu kemudian menghasilkan kembali nilai yang Anda berikanyield
dan jeda. Untuk contoh yang menunjukkan ini dengan lebih baik, mari kita gunakan beberapaprint
panggilan (ganti denganprint "text"
jika pada Python 2):Sekarang, masukkan dalam REPL:
Anda memiliki objek generator sekarang menunggu perintah untuk itu menghasilkan nilai. Gunakan
next
dan lihat apa yang dicetak:Hasil yang tidak dikutip adalah yang dicetak. Hasil yang dikutip adalah apa yang dikembalikan dari
yield
. Teleponnext
lagi sekarang:Generator ingat itu dijeda
yield value
dan dilanjutkan dari sana. Pesan berikutnya dicetak dan pencarianyield
pernyataan untuk menghentikannya dilakukan lagi (karenawhile
loop).sumber