# Mga generator sa Python: yield, iterator, at list at dictionary comprehension

URL: https://skillaria.top/tl/guides/python/generator-python-yield

Ang generator sa Python ay bagay na naglalabas ng mga halaga isa-isa, kapag hiningi lang, nang hindi itinatago ang lahat sa memorya; ginagawa ito ng function na may keyword na yield o ng generator expression sa loob ng mga bilog na bracket. Ang mga list, dictionary, at set comprehension ay maikling paraan para bumuo ng handang koleksiyon sa isang linya: [x * 2 for x in xs], {k: v for k, v in pairs}, {x for x in xs}.

Ang generator sa Python ay bagay na naglalabas ng mga halaga nang isa-isa, kapag hiniling lang, at hindi itinatago ang buong sunod-sunod na datos sa memorya. Dalawang paraan ang paggawa nito: gamit ang function na may keyword na `yield`, o gamit ang generator expression sa loob ng mga bilog na bracket. Kasama nito ang mga "list comprehension", "dict comprehension", at "set comprehension" — maikling paraan para bumuo ng handang koleksiyon sa isang linya. Tatalakayin natin kung paano gumagana ang python yield, ano ang pagkakaiba ng generator at iterator, at paano hindi gawing hindi na mabasang linya ang comprehension.

## Mga iterator: ang sandigan ng loop na for

Para maintindihan ang mga generator, kailangan munang unawain ang mga iterator. Ang iterable ay anumang bagay na maaaring daanan ng loop: listahan, string, dictionary, file. Ang iterator naman ay object na naaalala ang kasalukuyang posisyon, at sa bawat tawag ng `next()` ay nagbibigay ng susunod na elemento; kapag naubos na ang mga elemento, nagtataas ito ng `StopIteration`.

```
nums = [10, 20, 30]
it = iter(nums)      # kumuha ng iterator mula sa listahan
print(next(it))      # 10
print(next(it))      # 20
print(next(it))      # 30
next(it)             # StopIteration
```

Ginagawa mismo ng loop na `for` ang ganito: tinatawag nito ang `iter()`, at pagkatapos ang `next()` hanggang sa `StopIteration`. Maaaring gumawa ng sariling iterator gamit ang class na may mga method na `__iter__` at `__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] — nagpatuloy mula sa huling hintuan
```

Kapag narating ng function ang dulo (o ang `return`), kusang magtataas ng `StopIteration` ang generator, at maayos na natatapos ang loop na `for`. Dito nakasalalay ang pagkakaiba ng yield at return: tinatapos ng return ang function at nagbabalik ng isang halaga; sinasuspinde ng yield ang function at maaaring maglabas ng maraming halaga.

### Generator at iterator: mga pagkakaiba

Ang bawat generator ay iterator: mayroon itong `__iter__` at `__next__`. Pero hindi bawat iterator ay generator. Ang pagkakaiba ay nasa paraan ng paggawa at sa mga kakayahan.

|  | Iterator (class) | Generator |
| --- | --- | --- |

| Paano gawin | class na may `__iter__` at `__next__` | function na may `yield`, o `(x for x in ...)` |

| Estado | manu-manong nasa mga attribute | awtomatikong naiimbak sa mga lokal na variable |

| StopIteration | ikaw ang nagtataas | kusang lumilitaw kapag lumabas sa function |

| Karagdagan | ano ang isusulat mo | `send()`, `throw()`, `close()` |

Iisa ang mahalagang katangian ng pareho: isang beses lang sila madadaanan. Ang paulit-ulit na `for` sa generator na naubos na ay walang ibibigay — kailangang gumawa ng bago.

### Walang hanggang generator at pagbasa ng malalaking file

Hindi kailangang magtapos ang generator. Ligtas ang walang hanggang sunod-sunod basta't limitado ang bilang ng kukunin mula rito:

```
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']
```

Ang pinakapraktikal na gamit ay ang pagproseso ng malaking file nang linya-linya. Maaaring pagdugtungin ang mga generator sa isang pipeline, at sa bawat sandali, isang linya lang ang nasa memorya, hindi ang buong file:

```
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 at send

Ipinapasa ng `yield from` ang lahat ng halaga ng ibang iterable, kaya hindi na kailangan ng nested na loop:

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

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

Ipinapadala ng method na `send()` ang halaga papasok sa generator: nagiging resulta ito ng ekspresyong `yield`. Bago ang unang `send`, kailangang "simulan" muna ang generator gamit ang `next()`. Ganito isinusulat, halimbawa, ang pang-ipon ng average:

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

a = averager()
next(a)            # umabot sa unang yield
print(a.send(10))  # 10.0
print(a.send(20))  # 15.0
```

Bihira ang `send` sa praktikal na code, pero madalas itong itanong sa mga job interview.

### Ang range ba ay generator?

Hindi. Ang `range` ay lazy na sunod-sunod: hindi rin nito iniimbak ang lahat ng numero, pero sinusuportahan nito ang indexing, `len()`, pagsusuri gamit ang `in`, at maaaring daanan nang maraming beses. Wala sa mga ito ang kayang gawin ng generator.

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

## List comprehension: generator ng listahan

Ang list comprehension ay anyong `[expression for x in collection]`. Gumagawa ito ng bagong listahan at hindi binabago ang orihinal. Binabasa ito mula kaliwa pakanan: unang "ano ang ilalagay", pagkatapos "saan kukunin".

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

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

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

Sa kabila ng pangalang "generator ng listahan" sa orihinal na teksto, handang listahan ang resulta, hindi generator: kinakalkula agad ang lahat ng elemento.

### Generator ng listahan na may if at if else

Dito kadalasang nagkakamali. Ang kondisyong filter ay inilalagay sa dulo at nag-aalis ng mga elemento. Ang kondisyong pagpili (ternary operator) ay inilalagay sa unahan at ginagawang isa sa dalawang halaga ang bawat elemento, nang hindi nag-aalis ng kahit ano.

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

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

# filter at pagpili nang magkasama
odd_marks = ["pares" if n % 2 == 0 else "di-pares" for n in range(6) if n > 2]
# ['di-pares', 'pares', 'di-pares']
```

Ang `[a for a in amounts if a > 200 else 0]` ay syntax error: ang `else` ay nasa kondisyon lamang sa unahan.

### Nested na comprehension at dalawang-dimensiyong listahan

Ang dobleng `for` ay isinusulat sa parehong pagkakasunod ng nested na loop, mula itaas pababa. Nakatutulong ito para hindi malito sa pagdugtong ng listahan ng mga listahan:

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

Ang dalawang-dimensiyong listahan (matrix) ay ginagawa gamit ang comprehension sa loob ng comprehension. Karaniwang pagkakamali ang pagmu-multiply ng listahan: ang mga row ay magiging iisang object.

```
bad = [[0] * 3] * 2
bad[0][0] = 1
print(bad)   # [[1, 0, 0], [1, 0, 0]] — nagbago ang dalawang row

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

# transpose
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]]
```

## Dictionary at set comprehension

Pareho ang prinsipyo para sa ibang koleksiyon. Ang curly brace na may colon ay bumubuo ng dictionary (dict comprehension), at ang curly brace na walang colon ay bumubuo ng set (set comprehension).

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

categories = {cat for cat, _ in spent}
print(categories)  # {'pagkain', 'taksi'} — hindi ginagarantiya ang pagkakasunod ng set

last = {cat: amount for cat, amount in spent}
print(last)        # {'pagkain': 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()}
```

Pansinin ang `last`: kapag inulit ang key, ang huling halaga ang nananaig, kaya nawala ang 500. Kung kailangang pagsamahin ang mga halaga, hindi angkop ang comprehension — sumulat ng loop, o gamitin ang `collections.Counter` o `defaultdict`.

### Paano naman ang tuple comprehension?

Walang hiwalay na "tuple comprehension" sa Python. Ang mga bilog na bracket ay nagbibigay ng generator expression, at nakukuha ang tuple sa pamamagitan ng pagpasa nito sa `tuple()`:

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

## Generator expression: hindi nag-iimbak, kundi naglalabas

Kapag pinalitan ang mga square bracket ng bilog, nagiging generator expression ito. Wala itong kinakalkula nang maaga at naglalabas ng mga halaga isa-isa. Malaki ang pagkakaiba sa memorya kapag isang milyong elemento ang pinag-uusapan:

```
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))  # mga 8 MB
print(sys.getsizeof(as_gen))   # ilang daang byte
```

Kadalasang nakatira ang generator expression sa loob ng mga aggregate function. Kapag ito ang nag-iisang argumento, maaaring huwag nang isulat ang dagdag na bracket. Ang `any()` at `all()` ay humihinto sa unang sagot at hindi na dumadaan sa natitira.

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

total = sum(a for _, a in spent)            # 1000
has_big = any(a > 400 for _, a in spent)    # True, hindi na tumingin pagkatapos ng 500
biggest = max(spent, key=lambda p: p[1])    # ('pagkain', 500)
```

Simple ang tuntunin sa pagpili: kung kailangan ang resulta nang maraming beses, may indexing, o kailangan ang `len()`, gumamit ng listahan. Kung kailangan lang dumaan nang isang beses at magkalkula, gumamit ng generator.

## Saklaw ng variable: hindi tumatagas ang variable

Tanong na nakakalito kahit sa mga bihasa:

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

Ang variable ng loop sa loob ng comprehension ay nabubuhay sa sarili nitong saklaw at hindi nito pinapalitan ang panlabas na variable (ganito sa Python 3; iba ito sa Python 2). Sa karaniwang loop na `for`, pagkatapos nito ay magiging 2 ang `x`. Mula bersyon 3.12 (PEP 709), isinasama na ang mga comprehension sa nakapaligid na function at mas mabilis ang mga ito, pero nanatili ang tuntunin sa saklaw.

## Unpacking gamit ang * at ** sa comprehension: bago sa Python 3.15 (PEP 798)

Matagal nang maaaring gamitin ang asterisk para sa unpacking sa tawag ng function at sa literal ng listahan, pero sa loob ng comprehension, `SyntaxError` ito noon. Sa Python 3.15, inalis ang pagbabawal: ang `*elemento` ay nangangahulugang "ilagay dito ang lahat ng elemento nito". Gumagana ito sa mga listahan, set, at generator, pati sa mga asynchronous.

```
# 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)        # lazy na generator
```

Ginagawa rin ng double asterisk ang parehong bagay para sa mga dictionary. Kapaki-pakinabang ito para sa mga patong-patong na setting na "default → file → flag": tinatakpan ng bawat susunod na patong ang mga key ng nauna.

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

Dalawang paalala mula sa kurso. Hindi maaaring pagsamahin ang asterisk at ang conditional expression nang walang panaklong: ang `[*w if w else 0 for w in weeks]` ay syntax error; kailangang ilagay sa panaklong ang kondisyon. At hindi tatakbo ang ganitong code sa 3.14 pababa: kung sinusuportahan ng library ang mas lumang bersyon, manatili sa dobleng `for` o sa `itertools.chain.from_iterable`.

## Asynchronous na generator

Kapag may `yield` ang function na `async def`, nagiging asynchronous generator ito. Dinadaanan ito gamit ang `async for`, at sa loob nito ay maaaring maghintay ng network o disk gamit ang `await`. May asynchronous comprehension din.

```
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())
```

## Karaniwang pagkakamali

- **Comprehension na tatlong palapag.** Dalawang `for`, dalawang `if`, at ternary sa iisang linya — walang makakabasa nito pagkalipas ng isang linggo. Kapag hindi kasya sa isang linya, sumulat ng karaniwang loop: mas inuuna ng PEP 8 ang kalinawan kaysa sa ikli.
- **Pag-ulit sa generator nang dalawang beses.** Ang pangalawang `sum(gen)` ay magbabalik ng 0, dahil naubos na ang generator.
- **Comprehension para sa side effect.** Ang `[print(x) for x in xs]` ay bumubuo ng hindi kailangang listahan ng `None`. Para sa mga aksyon, gamitin ang loop na `for`.
- **Pag-asa ng listahan mula sa generator expression.** Ang `(x for x in xs)[0]` ay `TypeError`: walang index ang generator.

## Mga gawain sa generator para sa praktika

1. Sumulat ng generator na `fib()` na walang hanggang naglalabas ng mga numerong Fibonacci, at kunin ang unang 10 gamit ang `islice`.
2. Sa isang linya, kumuha ng dictionary na "salita → haba" para sa mga salitang mas mahaba sa tatlong letra.
3. Kalkulahin ang kabuuan ng mga parisukat ng mga pares na numero hanggang isang milyon, nang hindi gumagawa ng listahan.
4. Sumulat ng generator na `chunks(seq, n)` na naglalabas ng mga piraso ng listahan na tig-`n` elemento.

## Buod

Iniisa-isa ng iterator ang mga elemento gamit ang `next()`, at ang generator ang pinakasimpleng paraan para gumawa nito: function na may python yield, o expression sa mga bilog na bracket. Bumubuo ang list, dictionary, at set comprehension ng handang koleksiyon sa isang linya; ang filter na `if` ay nasa dulo, at ang pagpili na `if else` ay nasa unahan. Kapag kailangang tipirin ang memorya o isang beses lang iproseso ang datos, gamitin ang generator; kapag kailangan ang resulta nang maraming beses, gamitin ang listahan. Ang mga comprehension, generator, at ang bagong unpacking sa Python 3.15 ay tinalakay nang praktikal sa kurso, gamit ang tagasubaybay ng gastos.

## FAQ

**Maaari bang gumamit ng return sa generator?**

Oo. Ang return na walang halaga ay tumatapos lang sa generator; ang return na may halaga ay inilalagay ito sa attribute na value ng StopIteration, at ang halagang iyon ang natatanggap ng ekspresyong yield from.

**Paano gawing listahan ang generator?**

Ipasa ito sa list(): list(gen). Pagkatapos nito, naubos na ang generator, at nasa memorya na ang lahat ng halaga.

**Alin ang mas mabilis, list comprehension o loop na may append?**

Kadalasan bahagyang mas mabilis ang comprehension dahil hindi nito tinatawag ang append sa bawat ulit. Pero piliin ito ayon sa kalinawan ng code; bihira maging mapagpasya ang pagkakaiba sa bilis.

**Paano malalaman kung generator ang isang object?**

Gamitin ang inspect.isgenerator(obj) para sa object, o inspect.isgeneratorfunction(f) para sa function; para malaman kung iterator ang object, gamitin ang isinstance(obj, collections.abc.Iterator).

**Ano ang pagkakaiba ng generator expression at list comprehension?**

Agad na binubuo ng mga square bracket ang buong listahan sa memorya; ang mga bilog na bracket ay gumagawa ng generator na kumakalkula ng mga elemento kapag hiningi at dumadaan lamang nang isang beses.


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