# Jenereta katika Python: yield, iterators, na list na dict comprehensions

URL: https://skillaria.top/sw/guides/python/generatory-python

Jenereta katika Python ni kitu kinachotoa thamani moja moja kadiri zinavyoombwa, bila kuhifadhi mfuatano wote kwenye kumbukumbu; kinatengenezwa kwa kazi yenye neno kuu yield au kwa usemi wa jenereta kwenye mabano ya duara. Jenereta za orodha, kamusi na seti (comprehensions) ni njia fupi ya kujenga mkusanyiko uliokamilika kwa mstari mmoja: [x * 2 for x in xs], {k: v for k, v in pairs}, {x for x in xs}.

Jenereta katika Python ni kitu kinachotoa thamani moja kwa moja, kwa kadiri zinavyoombwa, na hakiweki mfuatano mzima kwenye kumbukumbu. Kinatengenezwa kwa njia mbili: kwa kazi yenye neno `yield`, au kwa usemi wa jenereta kwenye mabano ya duara. Pamoja nazo kuna comprehensions — njia fupi ya kujenga mkusanyiko uliokamilika kwa mstari mmoja. Hapa tutaangalia jinsi python yield inavyofanya kazi, jenereta inatofautianaje na iterator, na jinsi ya kutoandika comprehension isiyosomeka.

## Iterators: msingi wa mzunguko wa for

Ili kuelewa jenereta, kwanza tuelewe iterators. Kitu kinachoweza kupitiwa (iterable) ni chochote ambacho unaweza kukipitia kwa mzunguko: orodha, mfuatano, kamusi, faili. Iterator ni kitu kinachokumbuka nafasi yake ya sasa, na kwa wito wa `next()` hutoa kipengele kinachofuata; vitu vikiisha, hutupa `StopIteration`.

```
nums = [10, 20, 30]
it = iter(nums)      # pata iterator kutoka orodha
print(next(it))      # 10
print(next(it))      # 20
print(next(it))      # 30
next(it)             # StopIteration
```

Mzunguko wa `for` hufanya hasa hivi: huita `iter()`, kisha `next()` hadi `StopIteration`. Iterator yako mwenyewe inaweza kuandikwa kama darasa lenye mbinu `__iter__` na `__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] — tumeendelea kutoka pale tulipoishia
```

Kazi inapofika mwisho (au `return`), jenereta yenyewe hutupa `StopIteration`, na mzunguko wa `for` unamaliza kwa utulivu. Hapa ndipo tofauti kati ya yield na return ilipo: return humaliza kazi na kurudisha thamani moja, yield husimamisha kazi kwa muda na inaweza kutoa thamani nyingi.

### Jenereta na iterator: tofauti

Kila jenereta ni iterator: ina `__iter__` na `__next__`. Lakini si kila iterator ni jenereta. Tofauti iko katika njia ya kuunda na uwezo.

|  | Iterator (darasa) | Jenereta |
| --- | --- | --- |

| Jinsi ya kuunda | darasa lenye `__iter__` na `__next__` | kazi yenye `yield` au `(x for x in ...)` |

| Hali | kwa mkono, kwenye sifa | inahifadhiwa kiotomatiki kwenye vigezo vya ndani |

| StopIteration | unaitupa mwenyewe | inatokea yenyewe kazi inapomaliza |

| Zaidi ya hapo | unachoandika | `send()`, `throw()`, `close()` |

Wanachofanana ni sifa moja muhimu: zinaweza kupitiwa mara moja tu. `for` ya pili juu ya jenereta iliyokwisha haitatoa kitu — inahitajika jenereta mpya.

### Jenereta isiyo na mwisho na kusoma faili kubwa

Jenereta hailazimiki kuisha. Mfuatano usio na mwisho ni salama mradi unachukua idadi maalum ya vipengele tu:

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

Mfano wa vitendo zaidi ni kuchakata faili kubwa mstari kwa mstari. Jenereta zinaweza kuunganishwa kuwa mfumo wa mtiririko (pipeline), na kwenye kumbukumbu kutakuwa na mstari mmoja kwa wakati, si faili zima:

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

`yield from` hupitisha thamani zote za kitu kingine kinachoweza kupitiwa, bila mzunguko wa ndani:

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

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

Mbinu `send()` inatuma thamani ndani ya jenereta: inakuwa matokeo ya usemi wa `yield`. Kabla ya `send` ya kwanza, jenereta lazima "iwashwe" kwa `next()`. Kwa mfano, hivi ndivyo kikokotoo cha wastani kinavyoandikwa:

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

a = averager()
next(a)            # fika kwenye yield ya kwanza
print(a.send(10))  # 10.0
print(a.send(20))  # 15.0
```

Katika msimbo wa kawaida `send` hutumika mara chache, lakini mara nyingi huulizwa kwenye mahojiano ya kazi.

### range: je, ni jenereta?

Hapana. `range` ni mfuatano wa uvivu: pia haihifadhi namba zote, lakini inaruhusu kupata kwa faharasa, `len()`, ukaguzi wa `in`, na kupitiwa mara nyingi kadiri unavyotaka. Jenereta haina uwezo wowote kati ya hayo.

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

## List comprehension: orodha kwa mstari mmoja

List comprehension ni muundo `[usemi for x in mkusanyiko]`. Inaunda orodha mpya bila kubadilisha ile ya asili. Inasomwa kutoka kushoto kwenda kulia: kwanza "nini cha kuweka", kisha "kinatoka wapi".

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

# mzunguko wa kawaida
with_markup = []
for a in amounts:
    with_markup.append(a * 1.2)

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

Licha ya jina "jenereta" linalotumika mara nyingi, matokeo ya list comprehension ni orodha tayari, si jenereta: vipengele vyote vinakokotolewa mara moja.

### Jenereta ya orodha yenye sharti if na if else

Hapa ndipo watu wengi huchanganyikiwa. Sharti la kuchuja huwekwa mwishoni na huondoa vipengele. Sharti la kuchagua (opereta ya masharti) huwekwa mwanzoni na hubadilisha kila kipengele kuwa moja kati ya thamani mbili, bila kuondoa chochote.

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

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

# kuchuja na kuchagua pamoja
odd_marks = ["shufwa" if n % 2 == 0 else "witiri" for n in range(6) if n > 2]
# ['witiri', 'shufwa', 'witiri']
```

Muundo `[a for a in amounts if a > 200 else 0]` ni kosa la sintaksia: `else` inaruhusiwa tu kwenye sharti lililo mwanzoni.

### Orodha zilizoingiana na orodha ya pande mbili

For ya pili huandikwa kwa mpangilio uleule wa mizunguko iliyoingiana, kutoka juu kwenda chini. Hii inasaidia kutochanganya mpangilio unapounganisha orodha ya orodha:

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

Orodha ya pande mbili (matrix) hujengwa kwa comprehension ndani ya comprehension. Kosa la kawaida ni kuzidisha orodha: safu zitakuwa kitu kile kile.

```
bad = [[0] * 3] * 2
bad[0][0] = 1
print(bad)   # [[1, 0, 0], [1, 0, 0]] — safu zote mbili zimebadilika

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

# kubadilisha safu na nguzo
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]]
```

## Jenereta ya kamusi na ya seti

Mbinu hiyo hiyo inafanya kazi kwa mikusanyiko mingine. Mabano ya mikunjo yenye koloni hukusanya kamusi (dict comprehension), na bila koloni — seti (set comprehension).

```
spent = [("chakula", 500), ("teksi", 300), ("chakula", 200)]

categories = {cat for cat, _ in spent}
print(categories)  # {'chakula', 'teksi'} — mpangilio wa seti haujahakikishwa

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

Angalia `last`: ufunguo unaporudiwa, thamani ya mwisho ndiyo inayoshinda, na jumla ya 500 imepotea. Ikiwa unataka kujumlisha, comprehension haifai — andika mzunguko au tumia `collections.Counter` / `defaultdict`.

### Vipi kuhusu jenereta ya tuple?

Python haina "tuple comprehension" tofauti. Mabano ya duara yanatoa usemi wa jenereta, na tuple hupatikana kwa kuupitisha kwa `tuple()`:

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

## Usemi wa jenereta: kutohifadhi, bali kutoa

Ukibadilisha mabano ya mraba kuwa ya duara, unapata usemi wa jenereta. Haihesabu kitu mapema na inatoa thamani moja moja. Kwa milioni ya vipengele tofauti ya kumbukumbu ni kubwa sana:

```
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))  # takriban baiti milioni 8
print(sys.getsizeof(as_gen))   # mamia machache ya baiti
```

Mara nyingi usemi wa jenereta huishi ndani ya kazi za kujumlisha. Ikiwa jenereta ndiyo hoja pekee, mabano ya ziada hayahitajiki. `any()` na `all()` pia husimama kwenye jibu la kwanza na hazipitii kilichobaki.

```
spent = [("chakula", 500), ("teksi", 300), ("chakula", 200)]

total = sum(a for _, a in spent)            # 1000
has_big = any(a > 400 for _, a in spent)    # True, haiangalii zaidi ya 500
biggest = max(spent, key=lambda p: p[1])    # ('chakula', 500)
```

Kanuni rahisi ya uchaguzi: ikiwa matokeo yanahitajika mara kadhaa, au kuna faharasa au `len()`, chukua orodha. Ikiwa unahitaji kupitia mara moja na kuhesabu kitu, chukua jenereta.

## Upeo wa vigezo: kigezo hakivuji

Swali ambalo hata wazoefu hukwama nalo:

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

Kigezo cha mzunguko ndani ya comprehension kinaishi katika upeo wake na hakifuti cha nje (ndivyo ilivyo katika Python 3; Python 2 ilikuwa tofauti). Katika mzunguko wa kawaida wa `for`, baada ya mzunguko `x` ingekuwa 2. Kuanzia toleo la 3.12 (PEP 709) comprehensions zinaunganishwa ndani ya kazi inayozizunguka na zinafanya kazi kwa kasi zaidi, lakini kanuni ya upeo imebaki ile ile.

## Unpacking ya * na ** ndani ya comprehensions: mpya katika Python 3.15 (PEP 798)

Nyota ya kufungua (unpacking) imekuwa ikiandikwa katika wito wa kazi na katika orodha ya literal kwa muda mrefu, lakini ndani ya comprehension ilikuwa `SyntaxError`. Katika Python 3.15 kizuizi hicho kimeondolewa: `*kipengele` inamaanisha "weka hapa vipengele vyake vyote". Inafanya kazi katika orodha, seti na jenereta, pamoja na zile za async.

```
# 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}        # seti
lazy = (*g for g in groups)        # jenereta ya uvivu
```

Nyota mbili hufanya hivyo hivyo kwa kamusi. Ni rahisi kwa mipangilio ya tabaka: "chaguo-msingi → faili → bendera", ambapo kila tabaka linalofuata linafunika funguo za lile lililotangulia.

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

Tahadhari mbili kutoka kwenye kozi. Nyota haiwezi kuunganishwa na sharti la masharti bila mabano: `[*w if w else 0 for w in weeks]` ni kosa la sintaksia, sharti linapaswa kuwekwa kwenye mabano. Na msimbo kama huu hautaendeshwa kwenye 3.14 na matoleo ya chini: ikiwa maktaba inasaidia matoleo ya zamani, bakiza mzunguko wa for mara mbili au `itertools.chain.from_iterable`.

## Jenereta za asynchronous

Ikiwa kazi ya `async def` ina `yield`, unapata jenereta ya asynchronous. Inapitiwa kwa `async for`, na ndani yake unaweza kusubiri mtandao au diski kwa `await`. Pia kuna comprehensions za asynchronous.

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

## Makosa ya kawaida

- **Comprehension yenye ghorofa tatu.** For mbili, if mbili na opereta ya masharti kwenye mstari mmoja hakuna atakayesoma baada ya wiki. Usipoingia kwenye mstari mmoja, andika mzunguko wa kawaida: PEP 8 inaweka usomaji mbele ya ufupi.
- **Kupitia jenereta mara ya pili.** `sum(gen)` ya pili itarudisha 0, kwa sababu jenereta tayari imeisha.
- **Comprehension kwa ajili ya athari ya pembeni.** `[print(x) for x in xs]` inajenga orodha isiyo ya lazima ya `None`. Kwa vitendo tumia mzunguko wa `for`.
- **Kutarajia orodha kutoka usemi wa jenereta.** `(x for x in xs)[0]` ni `TypeError`: jenereta haina faharasa.

## Kazi za mazoezi kwa jenereta

1. Andika jenereta `fib()` inayotoa namba za Fibonacci bila kikomo, kisha chukua kumi za kwanza kwa `islice`.
2. Kwa mstari mmoja pata kamusi ya "neno → urefu" kwa maneno yenye herufi zaidi ya tatu.
3. Hesabu jumla ya miraba ya namba shufwa hadi milioni, bila kuunda orodha.
4. Andika jenereta `chunks(seq, n)` inayotoa vipande vya orodha vya `n` vipengele.

## Hitimisho

Iterator hutoa vipengele moja moja kupitia `next()`, na jenereta ndiyo njia rahisi ya kuiandika: kazi yenye python yield, au usemi kwenye mabano ya duara. Jenereta za orodha, kamusi na seti hukusanya mkusanyiko uliokamilika kwa mstari mmoja; sharti la kuchuja `if` huwekwa mwishoni, na uchaguzi `if else` mwanzoni. Ukitaka kuokoa kumbukumbu au kuchakata mtiririko wa data mara moja, chukua jenereta; ukihitaji matokeo mara nyingi, chukua orodha. Comprehensions, jenereta na unpacking mpya ya Python 3.15 zimefafanuliwa kwa vitendo katika kozi kwa mfano wa kifuatiliaji cha matumizi.

## FAQ

**Je, naweza kutumia return ndani ya jenereta?**

Ndiyo. return isiyo na thamani inahitimisha jenereta tu, na return yenye thamani huiweka kwenye sifa value ya StopIteration; thamani hiyo inapokelewa na usemi wa yield from.

**Ninawezaje kugeuza jenereta kuwa orodha?**

Pitisha kwa list(): list(gen). Kumbuka kwamba baada ya hapo jenereta imeisha, na thamani zote zinakuwa kwenye kumbukumbu.

**Ipi ni haraka zaidi: list comprehension au mzunguko wa append?**

Kwa kawaida comprehension ni kidogo haraka kwa sababu haitoi wito wa append kila mzunguko. Lakini chagua kwa usomaji; tofauti mara chache huamua.

**Ninawezaje kujua kwamba kitu ni jenereta?**

Tumia inspect.isgenerator(obj) kwa kitu au inspect.isgeneratorfunction(f) kwa kazi; kukagua kama kitu ni iterator, tumia isinstance(obj, collections.abc.Iterator).

**Usemi wa jenereta unatofautianaje na list comprehension?**

Mabano ya mraba hujenga orodha yote kwenye kumbukumbu mara moja; mabano ya duara huunda jenereta inayokokotoa vipengele inapoombwa, na inaweza kupitiwa mara moja tu.


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