Belajar dengan membaca secara berurutan

Generator Function dengan yield — Menghasilkan Nilai Satu per Satu untuk Menghemat Memori

Pelajari generator function Python dengan yield untuk mengembalikan nilai satu per satu dan menghemat memori.

Pada artikel sebelumnya kamu sudah melihat closure — cara menutup state di dalam fungsi dan mengembalikan nilai berbeda pada setiap panggilan. Python memiliki mekanisme khusus untuk fungsi semacam "kembalikan nilai berikutnya pada setiap panggilan" ini: generator function. Pakai yield alih-alih return, dan fungsi akan berhenti di tengah jalan untuk mengembalikan nilai, lalu melanjutkan dari titik berhenti pada panggilan berikutnya.

Ini cocok ketika kamu tidak ingin memuat koleksi besar sekaligus dan lebih suka mengalirkan satu nilai pada satu waktu — pemrosesan log, pengolahan data massal, dan workload serupa.

Dasar yield — Mengembalikan Satu Nilai pada Satu Waktu

Fungsi yang memiliki yield value di badannya adalah sebuah generator function. Memanggilnya seperti fungsi biasa tidak menjalankan badan — kamu hanya mendapat balik objek generator yang spesial.

Untuk benar-benar mengambil nilainya, panggil next(obj).

next() pertama menjalankan badan sampai yield pertama dan mengembalikan nilai itu.

next() berikutnya melanjutkan dari sana dan berjalan sampai yield berikutnya.

Begitu tidak ada lagi yield yang tersisa, next() lain akan memunculkan StopIteration.

def simple():
    yield 1
    yield 2

gen = simple()
print(type(gen))    # <class 'generator'>

print(next(gen))    # 1
print(next(gen))    # 2
print(next(gen))    # 3
# print(next(gen))  ← StopIteration (tidak ada yield lagi)
Bagaimana yield dan next() Berkaitan
gen = simple()(belum berjalan)objek generator dibuatnext(gen) → berhenti di yield 1→ kembalikan 1next(gen) → berhenti di yield 2→ kembalikan 2next() → tidak ada yield lagi, StopIterationkali ke-1kali ke-2ke-3 dan seterusnya

Beda dengan return

Fungsi biasa return dan seluruh scope-nya hilang — panggilan berikutnya dimulai dari atas. Generator function, sebaliknya, berhenti di yield dan menyimpan local serta posisinya. next() berikutnya melanjutkan tepat di tempat kamu berhenti, dan itulah perbedaan besarnya.

Bangun generator function yang menghasilkan ID pesanan 1, 2, 3 secara berurutan, dan ambil satu per satu dengan next().

① Definisikan def order_ids(): dengan tiga baris: yield 1 / yield 2 / yield 3.

② Bangun objek generator: gen = order_ids().

③ Panggil print(next(gen)) tiga kali berturut-turut dan pastikan 1 / 2 / 3 keluar.

(Saat jawaban benar, penjelasan akan muncul.)

Python Editor

Jalankan kode untuk melihat output

Mengambil Nilai dengan for — Tidak Perlu Khawatir Soal StopIteration

Menulis next() setiap saat itu melelahkan, dan kamu juga tidak seharusnya menangani StopIteration sendiri. Pakai for value in generator: sebagai gantinya, dan Python memanggil next() di balik layar dan keluar dari loop secara otomatis saat generator selesai. Ini bentuk yang jauh lebih umum.

Memasangkan dengan Loop for
for v in gen:Sisi generatoryield kembalikan dan jedaSisi pemanggilBadan loop pakai nilainyaBerhenti otomatissaat yield habisnextnilai

Jika kamu menyebar baris print("sedang berjalan...") di kedua sisi, kamu bisa melihat setiap yield mengalihkan eksekusi bolak-balik — sisi generator → sisi pemanggil → sisi generator.

def count_up_to(max_value):
    print("memulai generator")
    for i in range(max_value):
        print(f"  sebelum yield: {i}")
        yield i
        print(f"  sesudah yield: {i}")

for v in count_up_to(3):
    print(f"diterima: {v}")

# Alur output:
# memulai generator
#   sebelum yield: 0
# diterima: 0
#   sesudah yield: 0
#   sebelum yield: 1
# ... (berlanjut)

Pakai for untuk menerima data pelanggan dari generator yang mengalirkan satu per satu.

① Definisikan def each_customer(): dan loop dengan for name in ["Budi", "Siti", "Andi"]:, dengan men-yield name.

② Ambil nilai dengan for name in each_customer(): dan cetak f"Pelanggan berikutnya: {name}".

Jika ketiganya muncul berurutan, berarti selesai.

Python Editor

Jalankan kode untuk melihat output

Beda dengan list — Memangkas Penggunaan Memori

Saat kamu bekerja dengan nilai dari 0 hingga 999_999, list comprehension memuat seluruh 1.000.000 integer sekaligus di memori. Generator, sebaliknya, hanya menyimpan nilai saat ini dan menghitung yang berikutnya sesuai permintaan. List akan berakhir di kisaran beberapa MB; objek generator itu sendiri hanya beberapa ratus byte.

Jejak Memori list vs generator
list[0, 1, 2, ..., 999999]Beberapa MBdimuat sekaligusBagus saat butuhseluruhnyagenerator(i for i in range(...))Beberapa ratus byte(hanya item saat ini)Bagus untuk streamingsatu per satu
import sys

MAX = 10 ** 6

# List: memuat semuanya ke memori sekaligus
data_list = [i for i in range(MAX)]
print(sys.getsizeof(data_list))
# misalnya 8000056 (~8 MB)

# Generator expression: hanya item saat ini
data_gen = (i for i in range(MAX))
print(sys.getsizeof(data_gen))
# misalnya ~200 byte

# Kode di sisi pemanggil identik
for v in data_gen:
    if v > 2:
        break
    print(v)
# 0
# 1
# 2

Pintasan Generator Expression

Tukar tanda kurung siku [ ... ] pada list comprehension dengan tanda kurung biasa ( ... ) dan kamu mendapatkan generator expression. (i for i in range(1_000_000)) memberi efek yang sama dengan generator function berbasis def dalam satu baris. Teruskan langsung ke sum() / max() / any() dan kawan-kawan — semua bekerja.

Bangun generator expression (list comprehension dengan tanda kurung biasa, bukan kurung siku) untuk data harga, pastikan tipenya dengan type(), lalu ambil nilainya satu per satu dengan for.

① Bangun prices = (base * 100 for base in range(1, 6)). Triknya adalah memakai ( ) alih-alih [ ].

② Cetak tipenya dengan print(type(prices)) dan pastikan <class 'generator'> muncul.

③ Ambil nilai dengan for p in prices: dan cetak f"Harga: {p}".

Python Editor

Jalankan kode untuk melihat output

Merantai Generator dengan yield from

Saat kamu ingin generator meneruskan nilai dari generator lain apa adanya, kamu bisa menulis yield from sub_generator dalam satu baris alih-alih for v in sub: yield v. Ini berguna saat kamu ingin menggabungkan beberapa sumber data menjadi satu generator.

Misalnya, dengan fungsi yang mengalirkan penjualan untuk cabang Tokyo dan satu lagi untuk Osaka, menjajarkan yield from tokyo_sales() dan yield from osaka_sales() memberikan pemanggil generator yang terlihat seperti satu aliran berkelanjutan.

def tokyo_sales():
    yield 1200
    yield 980

def osaka_sales():
    yield 850
    yield 1340

def all_sales():
    yield from tokyo_sales()
    yield from osaka_sales()

for amount in all_sales():
    print(amount)
# 1200
# 980
# 850
# 1340
yield from Mendelegasikan ke Sub-Generator
Sisi pemanggilfor amountin all_sales()all_sales()generatorutamaUrutan diterima1200 → 980→ 850 → 1340①yield fromtokyo_sales()tokyo_sales()yield 1200yield 980②yield fromosaka_sales()osaka_sales()yield 850yield 1340menggerakkandelegasilanjut saat selesaidelegasi

yield from sub_gen() adalah kependekan dari for v in sub_gen(): yield v.

Nilai yang di-yield oleh sub langsung sampai ke pemanggil luar, sehingga

1200, 980 dari tokyo_sales,

lalu 850, 1340 dari osaka_sales

tiba di for amount in all_sales(): secara berurutan.

Bangun generator yang menggabungkan daftar inventaris per toko menjadi satu aliran tunggal.

① Definisikan def store_a(): dan for item in ["apel", "jeruk"]: yield item.

② Definisikan def store_b(): dan for item in ["pisang", "anggur", "stroberi"]: yield item.

③ Definisikan def all_items(): dan tulis yield from store_a() diikuti yield from store_b().

④ Iterasikan dengan for item in all_items(): print(item) untuk mencetak semua 5 secara berurutan.

Python Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Apa kecocokan terdekat untuk print(type(gen)) di sini?
def f():
yield 1
yield 2
gen = f()
print(type(gen))

Soal 2Apa yang terjadi saat kamu memanggil next() pada generator setelah setiap yield sudah habis?

Soal 3Manakah dari dua baris ini yang memakai memori jauh lebih sedikit?
A: data = [i for i in range(10**6)]
B: data = (i for i in range(10**6))