Sebuah file transaksi harian dapat berukuran ratusan megabyte dengan jutaan baris. Bila kita memuat seluruh isinya ke sebuah list, memori server bisa habis sebelum pipeline sempat berjalan. Iterator dan generator adalah mekanisme Python untuk memproses data satu baris pada satu waktu sehingga konsumsi memori tetap kecil, berapa pun ukuran file sumbernya. Kita mulai dari protokol iterator, lanjut ke generator dengan yield, lalu menutup dengan studi kasus CSV besar secara lazy.
Protokol Iterator: iter() dan next() #
Setiap perulangan for berjalan di atas protokol iterator: interpreter memanggil iter() untuk mendapatkan objek iterator, lalu next() berulang kali sampai StopIteration terangkat. Kode berikut menampilkannya secara eksplisit.
warna = ["merah", "kuning", "hijau"]
it = iter(warna)
print(next(it))
print(next(it))
print(next(it))
try:
next(it)
except StopIteration:
print("Iterator habis: StopIteration")Output yang Dihasilkan:
merah
kuning
hijau
Iterator habis: StopIterationMari kita telaah baris-baris kode di atas:
- Baris 3 (
it = iter(warna)): fungsiiter()memanggil method__iter__()milik list dan mengembalikan objek iterator penyimpan posisi baca. - Baris 4-6 (
print(next(it))): setiapnext()mengembalikan elemen pada posisi saat ini, lalu memajukan posisi internal satu langkah. - Baris 8-11 (
try/except): pemanggilannext()keempat mengangkatStopIteration. Perulanganformenangkap sinyal yang sama untuk berhenti tanpa galat.
Generator Function dengan yield #
Generator adalah cara paling ringkas membuat iterator: cukup tulis fungsi yang memakai yield alih-alih return. Saat yield dieksekusi, fungsi berhenti sementara dengan seluruh status lokalnya, lalu melanjutkan dari titik yang sama saat next() dipanggil lagi.
def hitung_mundur(n):
while n > 0:
yield n
n -= 1
for angka in hitung_mundur(3):
print(angka)
def transaksi_berikutnya(nama, jumlah):
for i in range(1, jumlah + 1):
yield {"id": i, "kasir": nama, "total": 15000 * i}
gen = transaksi_berikutnya("Siti", 3)
print(next(gen))
print(next(gen))Output yang Dihasilkan:
3
2
1
{'id': 1, 'kasir': 'Siti', 'total': 15000}
{'id': 2, 'kasir': 'Siti', 'total': 30000}Pemanggilan next(gen) pertama menghasilkan total: 15000 dan yang kedua total: 30000. Nilai i bertahan di antara dua pemanggilan — bukti generator menyimpan status eksekusinya di antara setiap yield.
Generator Expression dan Perbandingan Memori #
Python juga menyediakan generator expression — list comprehension yang memakai tanda kurung dan mengevaluasi secara lazy. Kode berikut membandingkan kedua bentuk pada satu juta baris transaksi milik Budi, lalu mengukur objeknya dengan sys.getsizeof.
from sys import getsizeof
def total_transaksi(n):
"""Aliran transaksi fiktif sebesar n baris."""
for i in range(n):
yield 10000 + (i % 5) * 2500
N = 1_000_000
pajak_list = [t * 0.11 for t in total_transaksi(N)]
pajak_gen = (t * 0.11 for t in total_transaksi(N))
print("N :", N)
print("getsizeof list :", getsizeof(pajak_list), "byte")
print("getsizeof generator:", getsizeof(pajak_gen), "byte")
print("total pajak (list) :", round(sum(pajak_list)))
print("total pajak (gen) :", round(sum(pajak_gen)))Output yang Dihasilkan:
N : 1000000
getsizeof list : 8448728 byte
getsizeof generator: 200 byte
total pajak (list) : 1650000000
total pajak (gen) : 1650000000Mari kita telaah baris-baris kode di atas:
- Baris 3-6 (
total_transaksi): generator ini mengalirkan satu juta nilai tanpa menyimpan satu pun di memori; hanya nilaiisaat ini yang hidup di frame eksekusi. - Baris 11 (
pajak_list = [...]): list comprehension mengevaluasi seluruh satu juta perkalian segera.getsizeofmelaporkan 8.448.728 byte untuk wadahnya saja — belum termasuk satu juta objek angka yang dirujuk. - Baris 12 (
pajak_gen = (...)): generator expression tidak menghitung apa pun saat dibuat; ukurannya 200 byte. - Baris 15-16 (
sum(...)): kedua total pajak identik (1650000000) — hasil tidak berubah, hanya strateginya: list menghitung semua di muka, generator menghitung saat nilainya dibutuhkan (lazy evaluation).
Studi Kasus: Membaca CSV Jutaan Baris Secara Lazy #
Sebagai penutup, kita terapkan pola yang sama pada kasus nyata: agregasi pendapatan per kota dari file CSV. Simulasi memakai 100.000 baris yang mewakili file produksi berjuta baris; tracemalloc mengukur puncak memori selama proses.
import csv
import random
import tracemalloc
from collections import defaultdict
rng = random.Random(42)
kota_pool = ["Jakarta", "Bandung", "Surabaya", "Medan", "Denpasar"]
kasir_pool = ["Budi", "Siti", "Dewi", "Rina"]
with open("transaksi.csv", "w", newline="") as f:
tulis = csv.writer(f)
tulis.writerow(["id", "kota", "kasir", "total"])
for i in range(1, 100_001):
kota = rng.choice(kota_pool)
kasir = rng.choice(kasir_pool)
total = rng.randrange(5000, 250_000, 500)
tulis.writerow([i, kota, kasir, total])
def baca_csv_lazy(nama_file):
with open(nama_file, newline="") as f:
for baris in csv.DictReader(f):
yield {"kota": baris["kota"], "total": int(baris["total"])}
tracemalloc.start()
jumlah = defaultdict(int)
pendapatan = defaultdict(int)
for t in baca_csv_lazy("transaksi.csv"):
jumlah[t["kota"]] += 1
pendapatan[t["kota"]] += t["total"]
puncak = tracemalloc.get_traced_memory()[1]
tracemalloc.stop()
print("Baris diproses : 100.000")
for kota in sorted(jumlah):
print(f"{kota:10s} jumlah={jumlah[kota]:6d} total=Rp{pendapatan[kota]:,}")
print(f"Puncak memori : {puncak / 1024:.1f} KB")Output yang Dihasilkan:
Baris diproses : 100.000
Bandung jumlah= 19945 total=Rp2,531,001,000
Denpasar jumlah= 20053 total=Rp2,545,740,000
Jakarta jumlah= 19976 total=Rp2,538,089,500
Medan jumlah= 20045 total=Rp2,541,225,500
Surabaya jumlah= 19981 total=Rp2,530,161,000
Puncak memori : 51.9 KBMari kita telaah baris-baris kode di atas:
- Baris 10-17 (
with open(...)): blok ini membuat file simulasi 100.000 baris. Seed tetaprandom.Random(42)membuat hasil uji dapat diulang identik. - Baris 20-23 (
baca_csv_lazy): generator pembaca file baris demi baris melaluicsv.DictReader. Pada satu waktu, hanya satu baris yang berupa dictionary kecil di memori. - Baris 26-33:
tracemalloc.start()mengaktifkan pelacakan memori. Perulangan pada baris 29 memproses aliran tanpa mengumpulkan baris ke list — setiap baris hanya menambah dua angka agregat (jumlahdanpendapatan). - Baris 36-38: hasil akhir dicetak per kota. Puncak memori hanya 51,9 KB — memori mengikuti ukuran agregat, bukan ukuran file.
Rujukan resmi: protokol iter()
, modul csv
, dan tracemalloc
. Perhatikan bahwa baca_csv_lazy memakai with open(...) — mekanisme with itulah fokus Chapter Context Managers berikutnya.