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.

python
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:

text
merah
kuning
hijau
Iterator habis: StopIteration

Mari kita telaah baris-baris kode di atas:

  1. Baris 3 (it = iter(warna)): fungsi iter() memanggil method __iter__() milik list dan mengembalikan objek iterator penyimpan posisi baca.
  2. Baris 4-6 (print(next(it))): setiap next() mengembalikan elemen pada posisi saat ini, lalu memajukan posisi internal satu langkah.
  3. Baris 8-11 (try/except): pemanggilan next() keempat mengangkat StopIteration. Perulangan for menangkap 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.

python
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:

text
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.

python
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:

text
N                  : 1000000
getsizeof list     : 8448728 byte
getsizeof generator: 200 byte
total pajak (list) : 1650000000
total pajak (gen)  : 1650000000

Mari kita telaah baris-baris kode di atas:

  1. Baris 3-6 (total_transaksi): generator ini mengalirkan satu juta nilai tanpa menyimpan satu pun di memori; hanya nilai i saat ini yang hidup di frame eksekusi.
  2. Baris 11 (pajak_list = [...]): list comprehension mengevaluasi seluruh satu juta perkalian segera. getsizeof melaporkan 8.448.728 byte untuk wadahnya saja — belum termasuk satu juta objek angka yang dirujuk.
  3. Baris 12 (pajak_gen = (...)): generator expression tidak menghitung apa pun saat dibuat; ukurannya 200 byte.
  4. 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.

python
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:

text
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 KB

Mari kita telaah baris-baris kode di atas:

  1. Baris 10-17 (with open(...)): blok ini membuat file simulasi 100.000 baris. Seed tetap random.Random(42) membuat hasil uji dapat diulang identik.
  2. Baris 20-23 (baca_csv_lazy): generator pembaca file baris demi baris melalui csv.DictReader. Pada satu waktu, hanya satu baris yang berupa dictionary kecil di memori.
  3. 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 (jumlah dan pendapatan).
  4. 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.