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.