Belajar dengan membaca secara berurutan

itertools dan functools — Toolkit Iterasi dan Komposisi Fungsi

Pelajari resep iterasi combinations/product/chain/accumulate, penguncian argumen partial, dan percepatan rekursi via memoisasi @lru_cache lewat contoh.

itertools adalah kumpulan fungsi yang membangun iterable baru dari iterable yang sudah ada (kombinasi, penggabungan, akumulasi), dan functools adalah kumpulan fungsi yang mentransformasi fungsi itu sendiri (binding argumen, memoisasi). Keduanya bisa menggantikan pola for loop + pelacakan state dalam satu baris, mengecilkan kode kamu secara dramatis.

Iterable — nilai yang bisa kamu loop

Sebagian besar fungsi itertools menerima "iterable" sebagai input dan mengembalikan iterable baru, jadi penting untuk memastikan dulu apa itu iterable. Iterable (yang bisa kamu tulis di sebelah kanan for x in ___:) adalah apa pun yang bisa kamu teruskan ke for loop, list(...), sum(...), atau map(...). list / tuple / str / dict / set / range, plus generator yang kamu tulis sendiri (fungsi memakai yield), semuanya berperilaku sebagai iterable di Python.

Iterable umum
list[1, 2, 3]tuple(1, 2, 3)str"abc"dict{"a": 1}set{1, 2, 3}rangerange(5)
Yang bisa kamu letakkan setelah for x in adalah iterable. Tipe built-in seperti list / tuple / str / dict / set / range adalah iterable, begitu juga generator kustom yang dibangun dengan yield.

itertools — toolkit kombinasi dan iterasi

itertools adalah modul yang mengumpulkan fungsi-fungsi yang membangun iterable baru dari yang sudah ada. Empat yang paling sering kamu pakai adalah combinations (kombinasi tanpa urutan), product (perkalian Cartesian atas beberapa set), chain (gabungkan iterable jadi satu), dan accumulate (running total atau running product). Mengetahui keempat ini memungkinkan kamu menulis ulang double for loop dan loop dengan pelacakan state jauh lebih bersih.

Empat fungsi inti itertools
combinationsKombinasi tanpa urutanproductSemua pasangan lintas setchainGabungkan iterableaccumulateRunning sum / product
combinations untuk pasangan / triple tanpa urutan, product untuk semua pasangan lintas set, chain untuk menggabungkan iterable, dan accumulate untuk running state saat memindai. Keempatnya mengembalikan iterator, jadi bungkus dengan list(...) untuk materialisasi.
FungsiInput → OutputContoh
combinations(it, k)Kombinasi k elemen (tanpa urutan)[A,B,C] → (A,B), (A,C), (B,C)
permutations(it, k)Permutasi k elemen (berurutan)[A,B] → (A,B), (B,A)
product(*its)Perkalian Cartesian lintas set[S,M] × [red,blue] → 4 kombo
chain(*its)Gabungkan beberapa iterable[1,2] + [3,4] → [1,2,3,4]
accumulate(it)Akumulasi dari kiri (sum default)[10,20,30] → [10, 30, 60]

Daftar pasangan dua minuman yang dipilih dari tiga opsi dan semua SKU ukuran × warna. combinations memberi pasangan tanpa urutan, product memberi perkalian Cartesian lintas set.

① Impor combinations dan product dari itertools

② Dari minuman ["coffee", "tea", "juice"], bangun pasangan 2 dengan combinations, materialisasi, dan cetak sebagai Pasangan: ◯

③ Bangun semua kombo dari ukuran ["S", "M", "L"] × warna ["red", "blue"] dengan product, materialisasi, dan cetak sebagai Semua SKU: ◯

(Jika kode kamu jalan dengan benar, penjelasannya akan muncul.)

Python Editor

Jalankan kode untuk melihat output

Gabungkan dua hari data penjualan dan hitung running total. chain menjahit beberapa iterable jadi satu dan accumulate memindai dari kiri, membawa nilai berjalan.

① Impor chain dan accumulate dari itertools

② Gabungkan penjualan hari 1 [120, 80] dan penjualan hari 2 [200, 150, 90] dengan chain, materialisasi, dan cetak sebagai Semua penjualan: ◯

③ Terapkan accumulate ke hasil yang digabung, materialisasi, dan cetak sebagai Total berjalan: ◯

Python Editor

Jalankan kode untuk melihat output

functools.partial — kunci argumen lebih awal

functools.partial membangun fungsi baru dengan beberapa argumen sudah dikunci. Ketika kamu perlu meneruskan fungsi dengan argumen yang terikat ke callback atau higher-order function, ini menyelamatkan kamu dari menulis fungsi wrapper satu baris seperti def wrapper(...): .... Di mana pun kamu memanggil fungsi yang sama berulang kali dengan variasi argumen kecil, partial memotong noise wrapper dan membuat kode lebih mudah dibaca.

from functools import partial

def format_with_unit(price, unit):
    return f"{price}{unit}"

# Bangun fungsi baru dengan unit "USD" sudah terikat
to_usd = partial(format_with_unit, unit="USD")

# Terapkan ke harga individual — sekarang hanya price yang perlu diteruskan
print(to_usd(100))   # 100USD
print(to_usd(200))   # 200USD
print(to_usd(300))   # 300USD
Cara kerja partial
power(base, exp)Mengembalikan base ** exppartial(power, exp=2)Kunci exp=2square(3) → 9square(5) → 25
partial(function, arg=value) mengembalikan fungsi baru dengan beberapa argumen terkunci. Kunci exp=2 pada power(base, exp) dan kamu mendapatkan fungsi square yang hanya butuh base.

Pakai partial untuk mengunci exp pada power(base, exp) dan turunkan fungsi square / cube khusus.

① Impor partial dari functools

② Definisikan def power(base, exp): yang mengembalikan `base ** exp

③ Dengan partial, bangun fungsi `square` dengan `exp=2` terkunci. Panggil square(3) dan square(5) dan cetak sebagai 3 kuadrat: ◯ / 5 kuadrat: ◯

④ Dengan partial, bangun fungsi `cube` dengan `exp=3` terkunci. Panggil cube(2) dan cetak sebagai 2 pangkat tiga: ◯

Python Editor

Jalankan kode untuk melihat output

functools.lru_cache — cache hasil dengan memoisasi

"Saya ingin memakai ulang hasil fungsi mahal yang dipanggil berulang dengan argumen yang sama" — muncul kapan pun output fungsi ditentukan oleh inputnya dan kecepatan penting. Membangunnya sendiri berarti mengelola cache dict sendiri, tetapi @lru_cache melakukannya dalam satu baris decorator.

@lru_cache adalah decorator yang men-cache nilai kembali fungsi. Ketika dipanggil lagi dengan argumen yang sama, ia mengembalikan nilai yang di-cache langsung, melewati perhitungan ulang. LRU (Least Recently Used — membuang entri yang tidak dipakai paling lama) membuang entri cache lama sehingga kamu bisa membatasi penggunaan memori dengan sesuatu seperti maxsize=128.

Cara kerja lru_cache
fib(30) pertama kaliJalankan tubuhCache hasil+ kembalikan nilaifib(30) kedua kaliBaca dari cache→ Kembali instan
Panggilan pertama miss cache, jadi tubuh fungsi jalan dan hasilnya disimpan sebagai (args → nilai kembali). Panggilan berikutnya dengan args yang sama kembali segera dari cache — tubuhnya tidak pernah jalan.

Jangan tambahkan ke fungsi dengan side effect

@lru_cache mengasumsikan "argumen yang sama → nilai kembali yang sama". Terapkan ke fungsi dengan side effect (membaca file / menulis ke DB / mengembalikan waktu sekarang) atau yang hasilnya bervariasi untuk input yang sama, dan kamu akan mendapat bug di mana hasil pertama terus kembali selamanya. Terapkan hanya pada fungsi murni (input sama → output sama, tanpa side effect).

Percepat Fibonacci rekursif dengan @lru_cache. Bahkan di fib(30) jumlah panggilan sangat besar tanpa cache, tetapi dengan decorator-nya instan.

① Impor lru_cache dari functools

② Definisikan fungsi fib(n) yang didekorasi dengan @lru_cache(maxsize=128) (kembalikan n jika n < 2, jika tidak kembalikan fib(n-1) + fib(n-2))

③ Cetak fib(30) sebagai fib(30): ◯

④ Cetak fib(50) sebagai fib(50): ◯ (tanpa caching, ini tidak akan selesai dalam waktu yang masuk akal)

⑤ Cetak maxsize: ◯ memakai fib.cache_info().maxsize

Python Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Apa cara paling ringkas untuk membangun perkalian Cartesian dari [1, 2, 3] dan [A, B]?

Soal 2Apa yang dikembalikan partial(f, x=10)?

Soal 3Mengapa @lru_cache mempercepat Fibonacci rekursif naif?