2.31. Iterator dan generator¶
Loop for melakukan lebih banyak pekerjaan dari yang terlihat. Halaman ini membahas protokol iterator yang mendasarinya, dan kata kunci yield yang memungkinkan Anda membuat iterator sendiri.
2.31.1. Protokol iterator¶
Setiap objek yang dapat diiterasi mengimplementasikan dua metode:
__iter__()-- mengembalikan sebuah iterator atas item-item objek.__next__()-- pada iterator, mengembalikan item berikutnya atau memunculkanStopIterationketika tidak ada lagi item.
Built-in iter() memanggil __iter__; next() memanggil __next__. Telusuri daftar secara manual:
it = iter([10, 20, 30])
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
print(next(it)) # raises StopIteration
for adalah singkatan dari "panggil __iter__ sekali, lalu loop pada __next__ hingga StopIteration."¶
Yang sebenarnya dilakukan oleh for x in items::
_it = iter(items)
while True:
try:
x = next(_it)
except StopIteration:
break
# ... loop body ...
Setiap list, tuple, string, dict, set, objek file, dan generator sudah mengimplementasikan __iter__ dan __next__ -- itulah mengapa semuanya bekerja dengan for.
2.31.2. yield dan fungsi generator¶
Fungsi yang mengandung pernyataan yield adalah fungsi generator. Memanggilnya tidak menjalankan badan fungsinya; melainkan mengembalikan objek generator (sebuah iterator) yang menjalankan badan fungsi satu yield pada satu waktu:
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
for value in count_up_to(3):
print(value)
Output:
0
1
2
Setiap panggilan ke next() melanjutkan fungsi hingga yield berikutnya, menyerahkan nilai tersebut ke pemanggil, dan berhenti di sana. Status lokal (i dalam kasus ini) dipertahankan antar pelanjutan.
next() menjalankan badan hingga yield berikutnya, menyerahkan nilai kembali, dan berhenti. Status lokal bertahan saat jeda.¶
Generator adalah cara termudah untuk menghasilkan urutan secara malas -- tidak ada list yang dibangun, item hanya dihitung ketika konsumen memintanya, dan fungsi dapat menghasilkan item selamanya jika diinginkan.
2.31.3. Pipeline malas¶
Generator dapat dikombinasikan dengan baik. Output satu generator dapat menjadi input generator lainnya:
def numbers():
i = 0
while True:
yield i
i += 1
def squares(source):
for x in source:
yield x * x
pipeline = squares(numbers())
for v in pipeline:
if v > 100:
break
print(v)
Nilai mengalir melalui pipeline satu per satu -- tidak ada list perantara, tidak ada batas atas yang ditentukan dalam numbers, dan konsumen (for v in pipeline) memutuskan kapan harus berhenti.
Setiap next() pada konsumen memicu satu pull melalui rantai; nilai hanya ada ketika sesuatu memintanya.¶
2.31.3.1. yield from¶
Loop yang menarik item dari iterable lain dan menghasilkan masing-masing item cukup umum sehingga Python menyediakan pintasan. Ekspresi yield from iter menghasilkan setiap nilai yang dihasilkan iterable tersebut, secara berurutan -- seolah-olah generator memiliki loop for x in iter: yield x secara langsung:
def chain(*sources):
for source in sources:
yield from source
for v in chain([1, 2, 3], (4, 5), "abc"):
print(v)
Output:
1
2
3
4
5
a
b
c
yield from persis setara dengan loop for eksplisit, hanya lebih pendek, dan ia menyebarkan StopIteration dari iterable dalam ke generator luar dengan bersih -- berguna ketika menggabungkan beberapa generator secara berurutan.
2.31.3.2. Ketika yield habis¶
Melewati akhir fungsi generator (atau menekan return eksplisit) secara otomatis memunculkan StopIteration. Tidak perlu memunculkannya secara manual; loop for yang mengitasinya akan melihatnya dan berhenti.
Gunakan generator ketika kode penghasil secara alami ditulis sebagai loop dengan beberapa titik yield; gunakan list comprehension biasa ketika Anda benar-benar membutuhkan seluruh urutan dalam memori.