Functional Programming HOWTO
Memahami iterator dan generator: kenapa Python bisa memproses data yang lebih besar dari memori.
Intisari
- Iterable = bisa dilewati
for. Iterator = sedang dilewati, dan hanya bisa sekali. - Generator (
yield) memproduksi nilai satu per satu โ memori tetap kecil berapa pun ukuran datanya. - Sebagian besar fungsi bawaan Python mengembalikan iterator lazy, bukan list.
itertoolsberisi alat siap pakai:chain,islice,groupby,batched.- Comprehension unggul karena mudah dibaca; kalau sudah tidak terbaca, kembalikan jadi loop biasa.
Iterable vs iterator
Dua istilah ini sering tertukar, padahal bedanya penting:
| Iterable | Iterator | |
|---|---|---|
| Artinya | Bisa dilewati oleh for | Sedang dalam proses dilewati |
| Contoh | list, str, dict, set | Generator, zip(), map() |
| Bisa berkali-kali? | Ya | Tidak โ sekali habis, habis |
| Punya panjang? | Biasanya ya | Tidak |
angka = [1, 2, 3] # iterable
sum(angka) # 6
sum(angka) # 6 โ masih bisa
g = (x for x in [1, 2, 3]) # iterator
sum(g) # 6
sum(g) # 0 โ sudah habis!
Ini bug yang sering menjebak. Kalau kamu perlu melewati data dua kali, jangan simpan
generator โ konversikan dulu: data = list(generator).
Generator
def baca_baris(path: Path):
with path.open(encoding="utf-8") as f:
for baris in f:
yield baris.strip()
for baris in baca_baris(Path("besar.log")):
proses(baris)
Fungsi apa pun yang mengandung yield otomatis jadi generator. Saat dipanggil, ia tidak
menjalankan isinya sama sekali โ hanya mengembalikan objek generator. Kode di dalamnya baru jalan saat
kamu mulai iterasi, dan berhenti (bukan selesai) di tiap yield.
Kenapa ini penting
# Memuat seluruh file ke memori โ 2 GB file = 2 GB RAM
baris = open("besar.log").readlines()
# Generator โ satu baris di memori pada satu waktu
for baris in baca_baris(Path("besar.log")):
...
Di pipeline RAG (Fase 7), pola ini yang menyelamatkanmu. Ingest ratusan PDF menjadi rantai generator: baca โ potong jadi chunk โ embed โ simpan. Tidak pernah ada tahap yang memuat seluruh korpus ke memori sekaligus.
Merangkai generator
def baca(paths):
for p in paths:
yield p.read_text(encoding="utf-8")
def potong(dokumen, ukuran=1000):
for teks in dokumen:
for i in range(0, len(teks), ukuran):
yield teks[i:i + ukuran]
def bersihkan(chunks):
for c in chunks:
c = c.strip()
if c:
yield c
# Tidak ada yang dieksekusi sampai baris terakhir mulai berjalan
pipeline = bersihkan(potong(baca(Path("docs").rglob("*.md"))))
for chunk in pipeline:
simpan(chunk)
Fungsi bawaan yang lazy
map(f, items) # iterator, bukan list
filter(p, items) # iterator
zip(a, b) # iterator โ berhenti di yang terpendek
enumerate(items) # iterator
reversed(items) # iterator
range(1_000_000) # tidak pernah membuat sejuta angka di memori
Kalau butuh hasilnya sebagai list, bungkus: list(map(f, items)).
Tapi: di Python, [f(x) for x in items] lebih idiomatik daripada
list(map(f, items)), dan [x for x in items if p(x)] lebih idiomatik
daripada list(filter(p, items)). map/filter baru menang
kalau f sudah berupa fungsi bernama.
itertools โ yang benar-benar berguna
from itertools import chain, islice, groupby, batched
# Sambung beberapa iterable jadi satu
chain(list_a, list_b, list_c)
# Ambil 10 pertama dari iterator apa pun (slicing tidak bisa untuk generator)
islice(generator_besar, 10)
# Bagi jadi kelompok berukuran tetap โ Python 3.12+
for grup in batched(dokumen, 100):
kirim_batch(grup) # penting untuk API yang punya batas ukuran request
# Kelompokkan โ INPUT HARUS SUDAH TERURUT berdasarkan key yang sama
for kategori, anggota in groupby(sorted(docs, key=kunci), key=kunci):
print(kategori, len(list(anggota)))
groupby hanya mengelompokkan yang berdampingan. Kalau input belum terurut,
hasilnya akan terpecah-pecah. Untuk pengelompokan umum, defaultdict(list) lebih aman
dan lebih jelas.
Kapan comprehension menjadi kurang terbaca
Bagian ini yang membuat dokumen HOWTO layak dibaca โ ia jujur soal batasnya:
# โ
terbaca dalam sekali lihat
aktif = [u for u in pengguna if u.aktif]
# โ ๏ธ mulai berat
hasil = [transform(x) for sub in matriks for x in sub if valid(x) and x.skor > 0.5]
# โ
tulis ulang jadi loop โ lebih panjang, jauh lebih terbaca
hasil = []
for sub in matriks:
for x in sub:
if valid(x) and x.skor > 0.5:
hasil.append(transform(x))
lambda: seperlunya saja
sorted(docs, key=lambda d: d.skor) # โ
pas โ sekali pakai, sangat pendek
f = lambda x: x * 2 # โ kalau diberi nama, pakai def saja
def f(x): return x * 2 # โ
punya nama di traceback
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.