Generator adalah fungsi yang menghasilkan nilai satu per satu, hanya saat diminta, bukan menyiapkan semuanya sekaligus. Bayangin dapur Warung Kopi Pak Darto. Cara pertama: semua pesanan dimasak di awal, lalu dijajarkan di meja. Mejanya penuh, dan pelanggan pertama ikut menunggu semuanya matang. Cara kedua: koki memasak satu pesanan tiap kali pelayan memanggil "berikutnya!", lalu diam menunggu panggilan berikutnya. Fungsi biasa yang mengembalikan list itu cara pertama. Generator itu cara kedua.
🧪 Tebakan cepat: generator
masakmencetakmasak kopi sususebelum menyerahkan tiap menu. Programnya menulisdapur = masak(...), laluprint("dapur siap"). Baris pertama yang tercetakmasak kopi susuataudapur siap? Tebak dulu, jawabannya ada di bagian "yield: berhenti sebentar".
yield: berhenti sebentar
def masak(pesanan):
for menu in pesanan:
print("masak", menu)
yield menu
dapur = masak(["kopi susu", "teh manis", "roti bakar"])
print("dapur siap")
print(next(dapur))
print(next(dapur))
dapur siap
masak kopi susu
kopi susu
masak teh manis
teh manis
- Fungsi yang di dalamnya ada
yieldjadi generator function. Memanggilnya belum menjalankan isinya.masak(...)cuma membuat objek generator, danprint(dapur)menulis<generator object masak at 0x...>. Karena itudapur siaptercetak duluan. Itu jawaban tebakannya. next(dapur)menjalankan isi fungsi sampai ketemuyield, menyerahkan nilainya, lalu berhenti di situ. Variabel di dalamnya tetap diingat.next(dapur)berikutnya melanjutkan tepat sesudahyieldtadi, ke putaran loop berikutnya.- Sifat ini disebut lazy: nilai baru dihitung saat diminta.
- Bandingkan dengan fungsi biasa yang mengumpulkan menu dengan
hasil.append(menu)lalureturn hasil: fungsi itu jalan sampaireturnbegitu dipanggil, jadi ketigamasak ...tercetak dulu, barudapur siap.
| Panggilan | Yang jalan di dalam masak |
Hasilnya |
|---|---|---|
masak([...]) |
belum ada | objek generator |
next(dapur) ke-1 |
cetak masak kopi susu, berhenti di yield |
kopi susu |
next(dapur) ke-2 |
lanjut ke putaran kedua, berhenti di yield |
teh manis |
next(dapur) ke-3 |
putaran ketiga, berhenti di yield |
roti bakar |
next(dapur) ke-4 |
loop habis, fungsi selesai | StopIteration |
Habis: StopIteration
Tambahkan dua baris print(next(dapur)) lagi di bawah program tadi. Yang ketiga masih dapat roti bakar, tapi yang keempat gak punya nilai lagi:
dapur siap
masak kopi susu
kopi susu
masak teh manis
teh manis
masak roti bakar
roti bakar
Traceback (most recent call last):
File "/home/darto/warung.py", line 11, in <module>
print(next(dapur))
~~~~^^^^^^^
StopIteration
StopIteration artinya generator sudah selesai: gak ada yield lagi yang bisa dicapai.
Dipakai di for, dan cuma sekali
dapur = masak(["kopi susu", "teh manis", "roti bakar"])
for menu in dapur:
print("antar", menu)
print("putaran kedua:")
for menu in dapur:
print("antar", menu)
print("selesai")
masak kopi susu
antar kopi susu
masak teh manis
antar teh manis
masak roti bakar
antar roti bakar
putaran kedua:
selesai
formemanggilnext()untukmu, dan berhenti diam-diam saat ketemuStopIteration, tanpa error.masakdanantarbergantian: tiap menu baru dimasak saat loop memintanya.- Generator cuma bisa dipakai sekali. Putaran kedua gak mencetak apa pun, karena generatornya sudah habis. Butuh lagi? Panggil
masak(...)lagi untuk generator baru. list(dapur)dan[x for x in dapur]juga menghabiskan generator: mengambil semua nilainya jadi list.
Deret Fibonacci: menggeser dua angka
Di deret Fibonacci, tiap angka adalah jumlah dua angka sebelumnya: 0, 1, 1, 2, 3, 5, 8, 13, dan seterusnya. Cukup dua variabel, a dan b, yang digeser tiap putaran dengan unpacking dari pelajaran tuple:
a, b = 0, 1
for _ in range(5):
print(a, b)
a, b = b, a + b
| Putaran | a |
b |
Sesudah a, b = b, a + b |
|---|---|---|---|
| 1 | 0 | 1 | a = 1, b = 0 + 1 = 1 |
| 2 | 1 | 1 | a = 1, b = 1 + 1 = 2 |
| 3 | 1 | 2 | a = 2, b = 1 + 2 = 3 |
| 4 | 2 | 3 | a = 3, b = 2 + 3 = 5 |
| 5 | 3 | 5 | a = 5, b = 3 + 5 = 8 |
- Ruas kanan,
b, a + b, dihitung dulu dengan nilai lama, baru ditempel keadanb. Jadi gak perlu variabel sementara. _adalah nama variabel biasa, dipakai kalau nilainya gak dibutuhkan.for _ in range(5)artinya "ulangi 5 kali".- Angka yang muncul di kolom
aadalah deretnya: 0, 1, 1, 2, 3. Kalauadi-yielddi tiap putaran, jadilah generator Fibonacci.
Generator expression
Mirip list comprehension, tapi pakai kurung biasa:
harga = [15000, 5000, 12000]
naik = (h + 2000 for h in harga)
print(next(naik)) # 17000
print(sum(naik)) # 21000
[...](list comprehension) itu eager: semua nilai dihitung sekarang dan disimpan di memori.(...)itu lazy: nilai dihitung satu per satu saat diminta.sum(naik)cuma menjumlahkan sisanya, 7000 + 14000, karena 17000 sudah diambilnext.{x * 2 for x in ...}(set comprehension) danlist(map(...))juga eager: semuanya dihitung dan disimpan sekaligus.
Bedanya terasa di data besar. Di Python 3.13, 64-bit:
import sys
daftar = [x ** 2 for x in range(10_000_000)]
gen = (x ** 2 for x in range(10_000_000))
print(sys.getsizeof(daftar)) # 89095160
print(sys.getsizeof(gen)) # 200
List-nya butuh sekitar 89 MB, generatornya 200 byte, berapa pun panjang deretnya. Garis bawah di 10_000_000 cuma pemisah ribuan supaya angkanya gampang dibaca.
⚠️ Jebakan yang sering ketemu
- Mengira memanggil generator langsung menjalankan isinya.
masak(pesanan)sendirian gak mencetak apa pun. Isinya baru jalan saatnext(),for, ataulist()meminta nilai. - Memakai generator dua kali. Loop kedua kosong, dan
list(dapur)yang kedua jadi[]. Buat generator baru, atau simpan hasilnya ke list kalau memang perlu diulang. returnditulis di tempatyield. Fungsinya berhenti di nilai pertama dan bukan generator lagi:[x for x in fibonacci(10)]gagal denganTypeError: 'int' object is not iterable.- Memperlakukan generator seperti list.
len(dapur)adalahTypeError: object of type 'generator' has no len(), dandapur[0]adalahTypeError: 'generator' object is not subscriptable. Perlu itu? Ubah dulu:list(dapur).
TL;DR: Fungsi yang berisi yield adalah generator: memanggilnya cuma membuat objek generator tanpa menjalankan isinya. next() menjalankannya sampai yield berikutnya, menyerahkan nilainya, lalu berhenti di situ; kalau gak ada yield lagi, keluar StopIteration. for memanggil next() untukmu dan berhenti diam-diam. Generator lazy dan cuma bisa dipakai sekali. Generator expression (x * 2 for x in ...) lazy dan hemat memori, sedangkan [...], {...}, dan list(map(...)) eager. Deret Fibonacci digeser dengan a, b = b, a + b.