Menu

Generator di Python: yield, iterasi lazy, dan generator expression

Cara generator menghasilkan nilai secara lazy di Python: kata kunci yield, generator expression, dan kapan generator lebih unggul daripada list biasa.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Fungsi yang bisa berhenti sejenak

Generator terlihat seperti fungsi biasa, tapi alih-alih menghitung seluruh hasil lalu mengembalikannya, generator menghasilkan (yield) satu nilai setiap kali, lalu berhenti sejenak di antara yield sampai pihak yang meminta menginginkan nilai berikutnya.

Contoh paling sederhana:

Perhatikan yield, bukan return. Saat for pertama kali meminta nilai, Python menjalankan badan fungsi sampai bertemu yield 1. Fungsi itu berhenti tepat di situ, menyerahkan 1 ke perulangan, dan mengingat persis di mana ia berhenti, lengkap dengan variabelnya. Iterasi berikutnya melanjutkan dari titik itu: current += 1, kembali ke while, yield 2. Begitu seterusnya sampai kondisi perulangan gagal, dan saat itu generator berhenti dengan sendirinya.

Berhenti lalu melanjutkan, itulah seluruh triknya.

Kenapa tidak membuat list saja?

Karena versi list menyiapkan semua nilainya di awal:

Tidak masalah untuk 5 item. Sekarang bayangkan kamu ingin 50 juta integer, dan kamu hanya peduli pada yang pertama yang cocok dengan suatu kondisi. Versi list menyiapkan 50 juta int lalu sebagian besarnya kamu buang. Versi generator hanya membuat sebanyak yang dipakai pemanggil. Saat for loop menemukan yang dicarinya lalu break, generator berhenti dengan sendirinya.

Itulah pola yang perlu kamu pahami: generator memungkinkan kamu menulis kode iterasi tanpa harus memutuskan di awal seberapa banyak hasil yang akan kamu butuhkan.

Generator expression

Kalau kamu pernah menulis list comprehension, kamu sudah tahu sintaksnya: ganti kurung siku dengan kurung biasa:

squares_gen belum menghitung apa pun. Ia hanyalah resep. Melakukan iterasi padanya menjalankan resep itu selangkah demi selangkah.

Generator expression sangat cocok sebagai argumen untuk fungsi yang memakai iterable:

Tidak ada list perantara. sum, max, dan any membaca nilai satu per satu, persis seperti yang mereka butuhkan.

Membaca file besar baris per baris

Inilah kasus nyata yang paling klasik untuk generator: memproses file yang terlalu besar untuk dimuat ke memori:

def parse_log_lines(path):
    with open(path) as f:
        for line in f:
            if line.startswith("ERROR"):
                yield line.rstrip()

for error in parse_log_lines("app.log"):
    print(error)

File-nya dibaca secara lazy. Setiap kali generator dipanggil, ia mengambil satu baris dari disk, menyaringnya, lalu melakukan yield. Pemakaian memori tetap datar berapa pun ukuran filenya.

Sekali pakai lalu habis

Generator hanya bisa dilalui sekali. Setelah kamu melakukan iterasi sampai akhir, generator itu habis:

Perulangan kedua tidak mencetak apa pun. Generator itu sudah tidak punya sisa.

Kalau kamu perlu iterasi lebih dari sekali, panggil lagi fungsi generator-nya untuk mendapat generator baru, atau simpan deretannya dengan list(...) lalu lakukan iterasi pada list itu berkali-kali. Pilih berdasarkan biayanya: membuat ulang tidak masalah kalau pekerjaannya ringan; list tidak masalah kalau deretannya kecil.

next() dan iterasi manual

Kamu tidak harus memakai for loop. next() mengambil satu nilai setiap kali:

StopIteration adalah cara generator memberi tanda "aku sudah selesai." for loop menangkapnya diam-diam. Di kode manual, kamu bisa memberikan nilai default ke next(gen, default) untuk menghindari exception itu.

Generator tak terbatas

Karena nilai dihasilkan saat dibutuhkan, generator bisa mewakili deretan yang tidak ada ujungnya, selama pemakainya berhenti meminta:

while True dengan yield di dalamnya tidak membuat program macet: artinya hanya "kalau ada yang terus meminta, terus hasilkan." Pemakainya yang memutuskan kapan berhenti.

Pola ini muncul di streaming data, event loop, dan di mana pun kamu mengambil nilai dari sumber yang panjangnya tidak tentu.

yield from: mendelegasikan ke iterable lain

Kalau generator-mu ingin melakukan yield untuk setiap nilai dari iterable lain, yield from melakukannya dalam satu baris:

Tanpa yield from, kamu akan menulis for loop bersarang dengan yield x di dalamnya. Ia juga meneruskan pemanggilan send() dan throw() dengan benar kalau kamu memakainya, tapi untuk kode sehari-hari, anggap saja artinya "yield setiap nilai dari benda ini."

Kapan memakai generator

Tiga tanda bahwa generator adalah alat yang tepat:

  1. Deretannya besar, mungkin tak terbatas, atau mahal untuk dihasilkan seluruhnya.
  2. Pemakainya mungkin berhenti sebelum akhir (misalnya break pada kecocokan pertama).
  3. Kamu ingin merangkai beberapa transformasi, seperti filter, map, dan take, tanpa membuat list perantara.

Dan kapan tidak memakainya:

  • Kamu butuh akses acak (seq[42]). Generator hanya bisa maju.
  • Kamu perlu melakukan iterasi pada deretan yang sama beberapa kali. Pakai list.
  • Deretannya kecil dan sudah kamu punya. List comprehension lebih sederhana.

Generator, list comprehension, dan list biasa masing-masing adalah jawaban yang tepat untuk pekerjaan yang berbeda. Keterampilannya adalah memilih salah satu tanpa terlalu banyak berpikir, dan cara tercepat membangun naluri itu adalah memperhatikan, untuk setiap iterasi yang kamu tulis, apakah "hasilkan semuanya dulu" atau "hasilkan satu per satu" yang lebih cocok.

Berikutnya: context manager lebih dalam

Sekarang kamu sudah melihat sebagian besar idiom yang dipakai Python untuk iterasi. Context manager, yaitu pernyataan with, adalah pembahasan berikutnya, dan context manager cocok dipadukan dengan generator untuk mengalirkan data dari file dan koneksi jaringan.

Pertanyaan yang sering diajukan

Apa itu generator di Python?

Generator adalah fungsi yang menghasilkan nilai satu per satu, berhenti sejenak di antara nilai-nilainya. Kamu menulisnya dengan def seperti fungsi biasa, tapi memakai yield, bukan return. Memanggilnya mengembalikan objek generator; setiap iterasi for atau setiap pemanggilan next() menjalankan fungsi itu sampai yield berikutnya.

Apa bedanya list dan generator?

List menyimpan semua elemen di memori sekaligus. Generator menghitung elemen saat dibutuhkan dan melupakannya setelah dipakai. Untuk deretan yang besar atau tak terbatas, generator hanya memakai sedikit memori yang tetap; untuk hasil kecil yang kamu butuhkan berulang kali, list lebih baik.

Bisakah aku melakukan iterasi pada generator dua kali?

Tidak. Generator habis setelah dilalui sampai akhir sekali; for loop kedua di atasnya tidak menghasilkan apa-apa. Kalau kamu perlu iterasi lebih dari sekali, panggil lagi fungsi generator-nya untuk mendapat generator baru, atau simpan hasilnya ke dalam list.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai