# Generator di Python: yield, iterator, serta list dan dict comprehension

URL: https://skillaria.top/id/guides/python/generator-python

Generator di Python adalah objek yang menghasilkan nilai satu per satu saat diminta, tanpa menyimpan seluruh urutan di memori; dibuat dengan fungsi berkata kunci yield atau ekspresi generator dalam tanda kurung. Generator list, dict, dan set (comprehension) adalah penulisan singkat untuk membangun koleksi jadi dalam satu baris: [x * 2 for x in xs], {k: v for k, v in pairs}, {x for x in xs}.

Generator di Python adalah objek yang menghasilkan nilai satu per satu, hanya ketika diminta, dan tidak menyimpan seluruh urutan di memori. Ia dibuat dengan dua cara: sebagai fungsi dengan kata kunci `yield`, atau sebagai ekspresi generator dalam tanda kurung. Berdekatan dengannya ada list, dict, dan set comprehension — penulisan singkat untuk membangun koleksi jadi dalam satu baris. Berikut dibahas cara kerja python yield, perbedaan generator dengan iterator, dan cara menulis comprehension yang tetap mudah dibaca.

## Iterator: fondasi perulangan for

Untuk memahami generator, kita perlu lebih dulu memahami iterator. Iterable adalah sesuatu yang bisa ditelusuri dengan perulangan: list, string, dict, berkas. Iterator adalah objek yang mengingat posisinya saat ini dan, setiap kali dipanggil `next()`, memberikan elemen berikutnya; jika elemen sudah habis, ia melempar `StopIteration`.

```
nums = [10, 20, 30]
it = iter(nums)      # ambil iterator dari list
print(next(it))      # 10
print(next(it))      # 20
print(next(it))      # 30
next(it)             # StopIteration
```

Perulangan `for` persis melakukan ini: memanggil `iter()`, lalu `next()` sampai `StopIteration`. Iterator sendiri bisa ditulis sebagai kelas dengan metode `__iter__` dan `__next__`:

```
class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current  0:
        yield start
        start -= 1

gen = countdown(3)
print(gen)          #
print(next(gen))    # 3
print(list(gen))    # [2, 1] — dilanjutkan dari titik berhenti
```

Ketika fungsi mencapai akhirnya (atau `return`), generator otomatis melempar `StopIteration`, dan perulangan `for` berakhir dengan rapi. Di sinilah perbedaan utama yield dan return: return mengakhiri fungsi dan mengembalikan satu nilai, sedangkan yield menjeda fungsi dan bisa menghasilkan nilai berapa pun banyaknya.

### Generator dan iterator: perbedaannya

Setiap generator adalah iterator: ia punya `__iter__` dan `__next__`. Tetapi tidak setiap iterator adalah generator. Perbedaannya ada pada cara pembuatan dan kemampuannya.

|  | Iterator (kelas) | Generator |
| --- | --- | --- |

| Cara membuat | kelas dengan `__iter__` dan `__next__` | fungsi dengan `yield` atau `(x for x in ...)` |

| Keadaan | diatur manual di atribut | disimpan otomatis di variabel lokal |

| StopIteration | harus Anda lemparkan sendiri | muncul sendiri saat fungsi selesai |

| Tambahan | sesuai yang Anda tulis | `send()`, `throw()`, `close()` |

Keduanya punya satu sifat penting yang sama: hanya bisa ditelusuri sekali. Perulangan `for` kedua pada generator yang sudah habis tidak menghasilkan apa-apa — buatlah generator baru.

### Generator tak terbatas dan membaca berkas besar

Generator tidak harus berakhir. Urutan tak terbatas aman selama Anda hanya mengambil sejumlah elemen tertentu dari situ:

```
from itertools import islice

def ids():
    n = 1
    while True:
        yield f"ID-{n:04d}"
        n += 1

print(list(islice(ids(), 3)))  # ['ID-0001', 'ID-0002', 'ID-0003']
```

Skenario yang paling praktis adalah memproses berkas besar baris demi baris. Generator bisa disambung menjadi pipa, sehingga pada setiap saat hanya satu baris yang ada di memori, bukan seluruh berkas:

```
def read_lines(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.rstrip("\n")

def only_errors(lines):
    for line in lines:
        if "ERROR" in line:
            yield line

for line in only_errors(read_lines("app.log")):
    print(line)
```

### yield from dan send

`yield from` meneruskan semua nilai dari iterable lain, sehingga perulangan bersarang tidak perlu ditulis:

```
def flatten(groups):
    for g in groups:
        yield from g

print(list(flatten([[1, 2], [3], [4, 5]])))  # [1, 2, 3, 4, 5]
```

Metode `send()` mengirim nilai ke dalam generator: nilai itu menjadi hasil dari ekspresi `yield`. Sebelum `send` pertama, generator harus dijalankan dulu dengan `next()`. Contohnya, penghitung rata-rata yang berjalan:

```
def averager():
    total, count = 0, 0
    avg = None
    while True:
        value = yield avg
        total += value
        count += 1
        avg = total / count

a = averager()
next(a)            # sampai yield pertama
print(a.send(10))  # 10.0
print(a.send(20))  # 15.0
```

Dalam kode aplikasi, `send` jarang dipakai, tetapi sering ditanyakan saat wawancara kerja.

### range: apakah itu generator?

Bukan. `range` adalah urutan malas: ia juga tidak menyimpan semua angka, tetapi mendukung indeks, `len()`, pengecekan `in`, dan bisa ditelusuri berkali-kali. Generator tidak bisa melakukan semua itu.

```
r = range(5)
print(r[2], len(r), 3 in r)  # 2 5 True
print(list(r), list(r))      # keduanya [0, 1, 2, 3, 4]
```

## List comprehension: generator list

List comprehension ditulis `[ekspresi for x in koleksi]`. Ia membuat list baru dan tidak mengubah yang asli. Bacanya dari kiri ke kanan: pertama «apa yang dimasukkan», lalu «dari mana diambil».

```
amounts = [100, 250, 400]

# perulangan biasa
with_markup = []
for a in amounts:
    with_markup.append(a * 1.2)

# sama dengan comprehension
with_markup = [a * 1.2 for a in amounts]
print(with_markup)  # [120.0, 300.0, 480.0]
```

Meskipun disebut «generator list», hasilnya adalah list biasa yang sudah jadi, bukan generator: semua elemen dihitung sekaligus.

### List comprehension dengan if dan if else

Di sinilah sering terjadi kebingungan. Kondisi filter ditaruh di akhir dan membuang elemen. Kondisi pemilihan (operator ternary) ditaruh di awal dan mengubah setiap elemen menjadi salah satu dari dua nilai, tanpa membuang apa pun.

```
amounts = [100, 250, 400]

big = [a for a in amounts if a > 200]                   # [250, 400]
labels = ["banyak" if a > 200 else "sedikit" for a in amounts]
# ['sedikit', 'banyak', 'banyak']

# filter dan pemilihan sekaligus
odd_marks = ["genap" if n % 2 == 0 else "ganjil" for n in range(6) if n > 2]
# ['ganjil', 'genap', 'ganjil']
```

Penulisan `[a for a in amounts if a > 200 else 0]` adalah kesalahan sintaks: `else` hanya ada pada kondisi di awal.

### List bersarang dan list dua dimensi

Dua `for` ditulis dalam urutan yang sama dengan perulangan bersarang, dari atas ke bawah. Ini membantu agar urutan tidak tertukar saat menggabungkan list dari list:

```
groups = [[1, 2], [3], [4, 5]]
flat = [x for g in groups for x in g]
# setara dengan:
# for g in groups:
#     for x in g:
#         flat.append(x)
print(flat)  # [1, 2, 3, 4, 5]
```

List dua dimensi (matriks) dibuat dengan comprehension di dalam comprehension. Kesalahan klasiknya adalah perkalian list: baris-barisnya menjadi objek yang sama.

```
bad = [[0] * 3] * 2
bad[0][0] = 1
print(bad)   # [[1, 0, 0], [1, 0, 0]] — kedua baris ikut berubah

grid = [[0] * 3 for _ in range(2)]
grid[0][0] = 1
print(grid)  # [[1, 0, 0], [0, 0, 0]]

# transposisi
m = [[1, 2, 3], [4, 5, 6]]
print([[row[i] for row in m] for i in range(3)])  # [[1, 4], [2, 5], [3, 6]]
```

## Generator dict dan set

Pola yang sama berlaku untuk koleksi lain. Kurung kurawal dengan titik dua membangun dict (dict comprehension), tanpa titik dua membangun set (set comprehension).

```
spent = [("makan", 500), ("taksi", 300), ("makan", 200)]

categories = {cat for cat, _ in spent}
print(categories)  # {'makan', 'taksi'} — urutan set tidak dijamin

last = {cat: amount for cat, amount in spent}
print(last)        # {'makan': 200, 'taksi': 300}

squares = {n: n ** 2 for n in range(4)}  # {0: 0, 1: 1, 2: 4, 3: 9}
swapped = {v: k for k, v in squares.items()}
```

Perhatikan `last`: jika kunci berulang, nilai terakhir yang menang, jadi jumlah 500 hilang. Jika ingin menjumlahkan, comprehension tidak cocok — tulis perulangan atau gunakan `collections.Counter` / `defaultdict`.

### Bagaimana dengan generator tuple?

Di Python tidak ada «tuple comprehension» tersendiri. Tanda kurung menghasilkan ekspresi generator, dan tuple dibuat dengan memberikannya ke `tuple()`:

```
t = tuple(n * 2 for n in range(3))  # (0, 2, 4)
```

## Ekspresi generator: menghasilkan, bukan menyimpan

Jika kurung siku diganti kurung biasa, hasilnya adalah ekspresi generator. Ia tidak menghitung apa pun di muka dan menghasilkan nilai satu per satu. Untuk satu juta elemen, perbedaan memorinya sangat besar:

```
import sys

as_list = [n for n in range(1_000_000)]
as_gen = (n for n in range(1_000_000))
print(sys.getsizeof(as_list))  # sekitar 8 MB
print(sys.getsizeof(as_gen))   # beberapa ratus byte
```

Ekspresi generator paling sering dipakai di dalam fungsi agregasi. Jika generator adalah satu-satunya argumen, tanda kurung tambahan tidak perlu ditulis. `any()` dan `all()` juga berhenti pada jawaban pertama dan tidak memeriksa sisanya.

```
spent = [("makan", 500), ("taksi", 300), ("makan", 200)]

total = sum(a for _, a in spent)            # 1000
has_big = any(a > 400 for _, a in spent)    # True, setelah 500 tidak diperiksa lagi
biggest = max(spent, key=lambda p: p[1])    # ('makan', 500)
```

Aturan memilihnya sederhana: jika hasilnya dibutuhkan berkali-kali, perlu indeks atau `len()`, pakai list. Jika cukup ditelusuri sekali untuk dihitung sesuatu, pakai generator.

## Ruang lingkup: variabel tidak bocor

Pertanyaan yang sering membuat bingung bahkan programmer berpengalaman:

```
x = 10
squares = [x for x in range(3)]
print(x)  # 10
```

Variabel perulangan di dalam comprehension hidup di ruang lingkupnya sendiri dan tidak menimpa variabel luar (ini berlaku di Python 3; di Python 2 perilakunya berbeda). Di perulangan `for` biasa, setelah selesai `x` akan bernilai 2. Sejak 3.12 (PEP 709) comprehension disisipkan ke dalam fungsi yang melingkupinya sehingga lebih cepat, tetapi aturan ruang lingkupnya tetap sama.

## Pembongkaran * dan ** di comprehension: baru di Python 3.15 (PEP 798)

Tanda bintang untuk pembongkaran sudah lama bisa ditulis di pemanggilan fungsi dan di literal list, tetapi di dalam comprehension itu menghasilkan `SyntaxError`. Di Python 3.15 larangan ini dicabut: `*elemen` berarti «masukkan di sini semua elemennya». Ini berfungsi di list, set, dan generator, termasuk yang asinkron.

```
# Python 3.15+
groups = [[1, 2], [3], [4, 5]]
flat = [*g for g in groups]        # [1, 2, 3, 4, 5]
uniq = {*g for g in groups}        # set
lazy = (*g for g in groups)        # generator malas
```

Tanda bintang ganda melakukan hal yang sama untuk dict. Ini nyaman untuk pengaturan berlapis «bawaan → berkas → flag»: setiap lapisan berikutnya menimpa kunci lapisan sebelumnya.

```
# Python 3.15+
layers = [{"debug": False, "port": 80}, {"port": 8080}, {"debug": True}]
config = {**layer for layer in layers}
# {'debug': True, 'port': 8080}
```

Dua catatan dari materi kursus. Tanda bintang tidak boleh digabung dengan ekspresi kondisional tanpa tanda kurung: `[*w if w else 0 for w in weeks]` adalah kesalahan sintaks, kondisinya harus dibungkus kurung. Dan kode seperti ini tidak akan berjalan di 3.14 ke bawah: jika pustaka Anda mendukung versi lama, pakai perulangan `for` ganda atau `itertools.chain.from_iterable`.

## Generator asinkron

Jika fungsi `async def` memakai `yield`, hasilnya adalah generator asinkron. Ia ditelusuri dengan `async for`, dan di dalamnya bisa menunggu jaringan atau disk dengan `await`. Ada juga comprehension asinkron.

```
import asyncio

async def ticks(n):
    for i in range(n):
        await asyncio.sleep(0.1)
        yield i

async def main():
    result = [i async for i in ticks(3)]
    print(result)  # [0, 1, 2]

asyncio.run(main())
```

## Kesalahan umum

- **Comprehension bertingkat tiga.** Dua `for`, dua `if`, dan ternary dalam satu baris tidak akan terbaca seminggu kemudian. Jika tidak muat dalam satu baris yang jelas, tulis perulangan biasa: PEP 8 mengutamakan keterbacaan di atas keringkasan.
- **Menelusuri generator dua kali.** `sum(gen)` kedua akan menghasilkan 0, karena generator sudah habis.
- **Comprehension demi efek samping.** `[print(x) for x in xs]` membangun list `None` yang tidak perlu. Untuk tindakan, gunakan perulangan `for`.
- **Mengharapkan list dari ekspresi generator.** `(x for x in xs)[0]` menghasilkan `TypeError`: generator tidak punya indeks.

## Latihan generator

1. Tulis generator `fib()` yang menghasilkan bilangan Fibonacci tanpa henti, lalu ambil 10 pertama dengan `islice`.
2. Dengan satu baris, buat dict «kata → panjang» untuk kata yang lebih dari tiga huruf.
3. Hitung jumlah kuadrat bilangan genap sampai satu juta tanpa membuat list.
4. Tulis generator `chunks(seq, n)` yang menghasilkan potongan list sepanjang `n` elemen.

## Ringkasan

Iterator memberikan elemen satu per satu lewat `next()`, dan generator adalah cara termudah untuk membuatnya: fungsi dengan python yield atau ekspresi dalam tanda kurung. Generator list, dict, dan set membangun koleksi jadi dalam satu baris; filter `if` ditaruh di akhir, pemilihan `if else` di awal. Jika ingin menghemat memori atau memproses aliran data sekali jalan, gunakan generator; jika hasilnya dibutuhkan berulang kali, gunakan list. Comprehension, generator, dan pembongkaran baru dari Python 3.15 dipraktikkan di kursus dengan contoh pencatat pengeluaran.

## FAQ

**Bolehkah memakai return di dalam generator?**

Boleh. return tanpa nilai hanya mengakhiri generator, sedangkan return dengan nilai disimpan di atribut value dari exception StopIteration; nilai itu diterima oleh ekspresi yield from.

**Bagaimana mengubah generator menjadi list?**

Berikan generator ke list(): list(gen). Setelah itu generator sudah habis, dan semua nilainya berada di memori.

**Mana yang lebih cepat, list comprehension atau perulangan dengan append?**

Biasanya comprehension sedikit lebih cepat, karena tidak memanggil metode append di setiap iterasi. Namun pilihlah berdasarkan keterbacaan; selisihnya jarang menentukan.

**Bagaimana mengetahui bahwa sebuah objek adalah generator?**

Gunakan inspect.isgenerator(obj) untuk objek, atau inspect.isgeneratorfunction(f) untuk fungsi. Untuk memastikan objek adalah iterator, gunakan isinstance(obj, collections.abc.Iterator).

**Apa bedanya ekspresi generator dan list comprehension?**

Kurung siku langsung membangun seluruh list di memori, sedangkan kurung biasa membuat generator yang menghitung elemen saat diminta dan hanya bisa ditelusuri sekali.


---
Source: Skillaria.Top (https://skillaria.top)
