# Python-இல் உருவாக்கிகள் (generators): yield, iterator-கள் மற்றும் பட்டியல், அகராதி உருவாக்கல்கள்

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

Python generator என்பது மதிப்புகளை ஒவ்வொன்றாக, கேட்கப்படும்போது மட்டுமே தரும் பொருள்; எல்லா மதிப்புகளையும் நினைவகத்தில் வைக்காது. அதை yield என்ற சொல்லுடன் கூடிய செயல்பாட்டாலோ, சுற்று அடைப்புக்குள் உள்ள உருவாக்கல் வெளிப்பாட்டாலோ உருவாக்கலாம். பட்டியல், அகராதி, set உருவாக்கல்கள் (comprehensions) ஒரே வரியில் தயார் தொகுப்பைக் கட்டும் சுருக்கமான வடிவம்: [x * 2 for x in xs], {k: v for k, v in pairs}, {x for x in xs}.

Python-இல் generator என்பது மதிப்புகளை ஒவ்வொன்றாக, கேட்கப்படும்போது மட்டுமே தரும் பொருள்; முழு வரிசையையும் நினைவகத்தில் வைத்திருக்காது. அதை இரண்டு வழிகளில் உருவாக்கலாம்: `yield` சொல்லுடன் கூடிய செயல்பாட்டாலோ, சுற்று அடைப்புக்குள் உள்ள உருவாக்கல் வெளிப்பாட்டாலோ. அருகில் "பட்டியல், அகராதி, set உருவாக்கல்கள்" (comprehensions) உள்ளன — ஒரே வரியில் தயார் தொகுப்பைக் கட்டும் சுருக்கமான வடிவம். python yield எப்படி வேலை செய்கிறது, generator iterator-இலிருந்து எப்படி வேறுபடுகிறது, comprehension-ஐப் படிக்க முடியாத வரியாக மாற்றாமல் எப்படி எழுதுவது என்று பார்ப்போம்.

## Iterator-கள்: for சுழல் எதன் மீது நிற்கிறது

Generator-களைப் புரிந்துகொள்ள முதலில் iterator-களைப் புரிந்துகொள்ள வேண்டும். Iterable என்பது சுழலால் கடக்கக்கூடியது: பட்டியல், சரம், அகராதி, கோப்பு. Iterator என்பது தற்போதைய நிலையை நினைவில் வைத்து, `next()` அழைப்பில் அடுத்த உறுப்பைத் தரும் பொருள்; உறுப்புகள் தீர்ந்ததும் `StopIteration` வீசும்.

```
nums = [10, 20, 30]
it = iter(nums)      # பட்டியலிலிருந்து iterator பெறு
print(next(it))      # 10
print(next(it))      # 20
print(next(it))      # 30
next(it)             # StopIteration
```

`for` சுழல் சரியாக இதையே செய்கிறது: `iter()`-ஐ அழைக்கும், பிறகு `StopIteration` வரும் வரை `next()`-ஐ. சொந்த iterator-ஐ `__iter__` மற்றும் `__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] — நிறுத்திய இடத்திலிருந்து தொடர்ந்தது
```

செயல்பாடு முடிவை அடையும்போது (அல்லது `return`-ஐ), generator தானாகவே `StopIteration` வீசும்; `for` சுழல் அமைதியாக முடியும். அதனால் yield மற்றும் return இடையேயான முக்கிய வேறுபாடு: return செயல்பாட்டை முடித்து ஒரே மதிப்பைத் தரும்; yield அதை இடைநிறுத்தி எத்தனை மதிப்புகளையும் தரலாம்.

### Generator மற்றும் iterator: வேறுபாடுகள்

எந்த generator-உம் iterator தான்: அதற்கு `__iter__` மற்றும் `__next__` உண்டு. ஆனால் எந்த iterator-உம் generator அல்ல. வேறுபாடு உருவாக்கும் வழியிலும் திறன்களிலும் உள்ளது.

|  | Iterator (வகுப்பு) | Generator |
| --- | --- | --- |

| எப்படி உருவாக்குவது | `__iter__` மற்றும் `__next__` கொண்ட வகுப்பு | `yield` கொண்ட செயல்பாடு அல்லது `(x for x in ...)` |

| நிலை | கையால் பண்புக்கூறுகளில் | உள்ளூர் மாறிகளில் தானாகச் சேமிக்கப்படும் |

| StopIteration | நீங்களே வீசுவீர்கள் | செயல்பாட்டிலிருந்து வெளியேறும்போது தானாகத் தோன்றும் |

| கூடுதலாக | நீங்கள் எழுதுவது | `send()`, `throw()`, `close()` |

இரண்டிற்கும் பொதுவான ஒரு முக்கியப் பண்பு: ஒருமுறை மட்டுமே கடக்க முடியும். தீர்ந்த generator மீது மீண்டும் `for` சுழல் எதையும் தராது — புதியதை உருவாக்க வேண்டும்.

### முடிவில்லாத generator மற்றும் பெரிய கோப்புகளைப் படித்தல்

Generator முடிவடைய வேண்டிய அவசியமில்லை. முடிவற்ற வரிசை, நீங்கள் வரையறுக்கப்பட்ட எண்ணிக்கை மட்டும் எடுக்கும் வரை பாதுகாப்பானது:

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

மிகவும் நடைமுறையான பயன்பாடு — பெரிய கோப்பை வரி வரியாகச் செயலாக்குவது. Generator-களை ஒரு குழாய்த்தொடராக (pipeline) இணைக்கலாம்; நினைவகத்தில் ஒவ்வொரு கணத்திலும் ஒரு வரி மட்டுமே இருக்கும், முழு கோப்பும் அல்ல:

```
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 மற்றும் send

`yield from` மற்றொரு iterable-இன் எல்லா மதிப்புகளையும் கடத்துகிறது; உள்ளமை சுழல் தேவையில்லை:

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

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

`send()` முறை generator-க்குள் ஒரு மதிப்பை அனுப்புகிறது: அது `yield` வெளிப்பாட்டின் முடிவாகிறது. முதல் `send`-க்கு முன் generator-ஐ `next()` மூலம் "தொடங்க" வேண்டும். உதாரணமாக சராசரியைக் கணக்கிடும் ஒன்று:

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

a = averager()
next(a)            # முதல் yield வரை செல்
print(a.send(10))  # 10.0
print(a.send(20))  # 15.0
```

நடைமுறைக் குறியீட்டில் `send` அரிதாகவே வரும், ஆனால் நேர்காணல்களில் அடிக்கடி கேட்கப்படும்.

### range ஒரு generator-ஆ?

இல்லை. `range` ஒரு சோம்பேறி வரிசை: அதுவும் எல்லா எண்களையும் வைத்திருப்பதில்லை; ஆனால் குறியீட்டிடுதல், `len()`, `in` சோதனை மற்றும் எத்தனை முறை வேண்டுமானாலும் கடக்கும் திறன் உண்டு. Generator இவற்றில் எதையும் செய்யாது.

```
r = range(5)
print(r[2], len(r), 3 in r)  # 2 5 True
print(list(r), list(r))      # இரு முறையும் [0, 1, 2, 3, 4]
```

## பட்டியல் உருவாக்கல்: list comprehension

பட்டியல் உருவாக்கல் (list comprehension) — `[வெளிப்பாடு for x in தொகுப்பு]` வடிவம். அது புதிய பட்டியலை உருவாக்கும், மூலத்தை மாற்றாது. இடமிருந்து வலமாகப் படிக்கலாம்: முதலில் "என்ன போடுவது", பிறகு "எங்கிருந்து எடுப்பது".

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

# சாதாரண சுழல்
with_markup = []
for a in amounts:
    with_markup.append(a * 1.2)

# அதே comprehension மூலம்
with_markup = [a * 1.2 for a in amounts]
print(with_markup)  # [120.0, 300.0, 480.0]
```

"பட்டியல் உருவாக்கி" என்ற பெயர் இருந்தாலும் முடிவு தயார் பட்டியல், generator அல்ல: எல்லா உறுப்புகளும் உடனே கணக்கிடப்படுகின்றன.

### பட்டியல் உருவாக்கலில் if மற்றும் if else நிபந்தனை

இங்குதான் அதிகம் குழப்பம். வடிகட்டி நிபந்தனை இறுதியில் வந்து உறுப்புகளை நீக்கும். தேர்வு நிபந்தனை (ternary) தொடக்கத்தில் வந்து ஒவ்வொரு உறுப்பையும் இரண்டில் ஒரு மதிப்பாக மாற்றும், எதையும் நீக்காமல்.

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

big = [a for a in amounts if a > 200]                   # [250, 400]
labels = ["அதிகம்" if a > 200 else "குறைவு" for a in amounts]
# ['குறைவு', 'அதிகம்', 'அதிகம்']

# வடிகட்டியும் தேர்வும் சேர்ந்து
odd_marks = ["இரட்டை" if n % 2 == 0 else "ஒற்றை" for n in range(6) if n > 2]
# ['ஒற்றை', 'இரட்டை', 'ஒற்றை']
```

`[a for a in amounts if a > 200 else 0]` என்பது தொடரியல் பிழை: `else` தொடக்கத்தில் உள்ள நிபந்தனைக்கு மட்டுமே உண்டு.

### உள்ளமை பட்டியல் உருவாக்கல்களும் இரு-பரிமாண பட்டியலும்

இரட்டை `for` உள்ளமை சுழல்களின் அதே வரிசையில், மேலிருந்து கீழாக எழுதப்படுகிறது. பட்டியல்களின் பட்டியலை ஒன்றாக்கும்போது வரிசையைக் குழப்பாமல் இருக்க இது உதவும்:

```
groups = [[1, 2], [3], [4, 5]]
flat = [x for g in groups for x in g]
# சமமானது:
# for g in groups:
#     for x in g:
#         flat.append(x)
print(flat)  # [1, 2, 3, 4, 5]
```

இரு-பரிமாண பட்டியலை (அணியை) comprehension-க்குள் comprehension உருவாக்கும். பட்டியலைப் பெருக்குவதில் உள்ள உன்னதப் பிழை: வரிசைகள் ஒரே பொருளாகி விடும்.

```
bad = [[0] * 3] * 2
bad[0][0] = 1
print(bad)   # [[1, 0, 0], [1, 0, 0]] — இரு வரிசைகளும் மாறின

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

## அகராதி மற்றும் set உருவாக்கல்கள்

அதே முறை மற்ற தொகுப்புகளுக்கும் வேலை செய்கிறது. காற்புள்ளியுடன் கூடிய சுருள் அடைப்புகள் அகராதியை (dict comprehension) உருவாக்கும்; காற்புள்ளி இல்லாதவை set-ஐ (set comprehension).

```
spent = [("உணவு", 500), ("டாக்ஸி", 300), ("உணவு", 200)]

categories = {cat for cat, _ in spent}
print(categories)  # {'உணவு', 'டாக்ஸி'} — set-இன் வரிசை உத்தரவாதம் அல்ல

last = {cat: amount for cat, amount in spent}
print(last)        # {'உணவு': 200, 'டாக்ஸி': 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()}
```

`last`-ஐக் கவனியுங்கள்: விசை திரும்பும்போது கடைசி மதிப்பு வெல்லும்; 500 தொகை இழக்கப்பட்டது. கூட்ட வேண்டுமென்றால் comprehension பொருந்தாது — சுழல் எழுதுங்கள், அல்லது `collections.Counter` / `defaultdict` பயன்படுத்துங்கள்.

### tuple உருவாக்கல் பற்றி?

Python-இல் தனியான "tuple comprehension" இல்லை. சுற்று அடைப்புகள் generator வெளிப்பாட்டைத் தருகின்றன; tuple-ஐ அதை `tuple()`-க்கு அனுப்புவதன் மூலம் பெறுகிறோம்:

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

## Generator வெளிப்பாடு: சேமிக்காமல் கொடுப்பது

சதுர அடைப்புகளுக்குப் பதிலாக சுற்று அடைப்புகளை வைத்தால் generator வெளிப்பாடு கிடைக்கும். அது எதையும் முன்கூட்டியே கணக்கிடாது, மதிப்புகளை ஒவ்வொன்றாகத் தரும். ஒரு மில்லியன் உறுப்புகளுக்கு நினைவக வேறுபாடு மிகப் பெரியது:

```
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))  # சுமார் 8 MB
print(sys.getsizeof(as_gen))   # சில நூறு பைட்டுகள்
```

பெரும்பாலும் generator வெளிப்பாடுகள் கூட்டுச் செயல்பாடுகளின் உள்ளே வாழும். Generator ஒரே வாதமாக இருந்தால் கூடுதல் அடைப்புகள் தேவையில்லை. `any()` மற்றும் `all()` முதல் பதிலிலேயே நின்றுவிடும், மீதியைச் சுற்றாது.

```
spent = [("உணவு", 500), ("டாக்ஸி", 300), ("உணவு", 200)]

total = sum(a for _, a in spent)            # 1000
has_big = any(a > 400 for _, a in spent)    # True, அடுத்து 500-ஐப் பார்க்காது
biggest = max(spent, key=lambda p: p[1])    # ('உணவு', 500)
```

தேர்வு விதி எளிது: முடிவு பல முறை தேவைப்பட்டால், குறியீட்டிடுதல் அல்லது `len()` தேவைப்பட்டால் — பட்டியலை எடுங்கள். ஒரு முறை கடந்து ஏதாவது கணக்கிட வேண்டுமென்றால் — generator.

## Scope: மாறி வெளியே கசியாது

நன்கு அனுபவம் உள்ளவர்களையும் தடுமாற வைக்கும் கேள்வி:

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

Comprehension-க்குள் உள்ள சுழல் மாறி அதன் சொந்தப் பரப்பில் வாழ்கிறது; வெளிப்புற மாறியை மிதிக்காது (Python 3-இல்; Python 2-இல் அது வேறுவிதமாக இருந்தது). சாதாரண `for` சுழலுக்குப் பின் `x` 2 ஆக இருந்திருக்கும். Python 3.12 (PEP 709) முதல் comprehension-கள் சுற்றியுள்ள செயல்பாட்டுக்குள் பதிக்கப்பட்டு வேகமாக இயங்குகின்றன, ஆனால் பார்வை விதி அப்படியே உள்ளது.

## Comprehension-களில் * மற்றும் ** unpacking: Python 3.15-இல் புதியது (PEP 798)

Unpacking நட்சத்திரக்குறியை செயல்பாட்டு அழைப்பிலும் பட்டியல் literal-இலும் நீண்ட காலமாக எழுதலாம்; ஆனால் comprehension-க்குள் அது `SyntaxError`. Python 3.15-இல் அந்தத் தடை நீங்கியது: `*உறுப்பு` என்பது "இங்கே அதன் எல்லா உறுப்புகளையும் செருகு" என்று பொருள். பட்டியல்கள், set-கள் மற்றும் generator-களில் வேலை செய்யும், 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)        # சோம்பேறி generator
```

இரட்டை நட்சத்திரக்குறி அகராதிகளுக்கும் அதே வேலையைச் செய்கிறது. இது "இயல்பு → கோப்பு → கொடிகள்" என்ற அடுக்கு அமைப்புகளுக்கு வசதியானது: ஒவ்வொரு அடுத்த அடுக்கும் முந்தையவற்றின் விசைகளை மேலெழுதும்.

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

பாடநெறியின் இரண்டு எச்சரிக்கைகள். நட்சத்திரக்குறியை அடைப்பில்லாத நிபந்தனை வெளிப்பாட்டுடன் சேர்க்கக் கூடாது: `[*w if w else 0 for w in weeks]` தொடரியல் பிழை, நிபந்தனையை அடைப்பிட வேண்டும். மேலும் இந்தக் குறியீடு 3.14 மற்றும் அதற்குக் கீழே இயங்காது: நூலகம் பழைய பதிப்புகளை ஆதரித்தால் இரட்டை `for`-ஐ அல்லது `itertools.chain.from_iterable`-ஐ வைத்திருங்கள்.

## ஒத்திசைவற்ற generator-கள்

`async def` செயல்பாட்டில் `yield` இருந்தால் அது ஒத்திசைவற்ற generator ஆகிறது. அதன் மீது `async for` சுழல் செல்லும்; உள்ளே பிணையம் அல்லது வட்டுக்காக `await` மூலம் காத்திருக்கலாம். ஒத்திசைவற்ற comprehension-களும் உண்டு.

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

## பொதுவான தவறுகள்

- **மூன்று மாடிக் comprehension.** இரண்டு `for`, இரண்டு `if` மற்றும் ternary ஒரே வரியில் — ஒரு வாரம் கழித்து யாரும் படிக்க மாட்டார்கள். வரியில் அடங்காவிட்டால் சாதாரண சுழல் எழுதுங்கள்: PEP 8 சுருக்கத்தை விட வாசிப்புத்திறனுக்கு முன்னுரிமை தருகிறது.
- **generator-ஐ மீண்டும் கடப்பது.** இரண்டாவது `sum(gen)` 0 தரும், ஏனெனில் generator ஏற்கனவே தீர்ந்துவிட்டது.
- **பக்க விளைவுக்காக comprehension.** `[print(x) for x in xs]` தேவையற்ற `None`-களின் பட்டியலை உருவாக்கும். செயல்களுக்கு `for` சுழல்.
- **generator வெளிப்பாட்டிலிருந்து பட்டியலை எதிர்பார்ப்பது.** `(x for x in xs)[0]` — `TypeError`: generator-க்கு குறியீடுகள் இல்லை.

## பயிற்சிக்கான generator பணிகள்

1. முடிவில்லாமல் ஃபிபோனாச்சி எண்களைத் தரும் `fib()` generator-ஐ எழுதி, `islice` மூலம் முதல் 10-ஐ எடுங்கள்.
2. மூன்று எழுத்துகளுக்கு மேல் உள்ள சொற்களுக்கு "சொல் → நீளம்" அகராதியை ஒரே வரியில் பெறுங்கள்.
3. பட்டியல் உருவாக்காமல், ஒரு மில்லியன் வரையிலான இரட்டை எண்களின் வர்க்கங்களின் கூட்டுத்தொகையைக் கணக்கிடுங்கள்.
4. பட்டியலை `n` உறுப்புகள் கொண்ட துண்டுகளாகத் தரும் `chunks(seq, n)` generator-ஐ எழுதுங்கள்.

## சுருக்கம்

Iterator `next()` மூலம் உறுப்புகளை ஒவ்வொன்றாகத் தருகிறது; generator அதை எழுதுவதற்கான எளிய வழி: python yield கொண்ட செயல்பாடு அல்லது சுற்று அடைப்பு வெளிப்பாடு. பட்டியல், அகராதி, set உருவாக்கல்கள் தயார் தொகுப்பை ஒரே வரியில் கட்டுகின்றன; `if` வடிகட்டி இறுதியில், `if else` தேர்வு தொடக்கத்தில் வரும். நினைவகத்தைச் சேமிக்க அல்லது தரவு ஓட்டத்தை ஒரு முறை செயலாக்க — generator; முடிவைப் பல முறை பயன்படுத்த — பட்டியல். Comprehension-கள், generator-கள் மற்றும் Python 3.15-இன் புதிய unpacking ஆகியவை செலவு கண்காணிப்பானின் எடுத்துக்காட்டில் பாடநெறியில் நடைமுறையில் விளக்கப்பட்டுள்ளன.

## FAQ

**generator-இல் return பயன்படுத்தலாமா?**

ஆம். மதிப்பில்லாத return generator-ஐ வெறுமனே முடிக்கும்; மதிப்புடன் கூடிய return அதை StopIteration விதிவிலக்கின் value பண்புக்கூறில் வைக்கும்; அந்த மதிப்பை yield from வெளிப்பாடு பெறும்.

**generator-ஐ பட்டியலாக மாற்றுவது எப்படி?**

அதை list()-க்கு அனுப்புங்கள்: list(gen). அதன் பின் generator தீர்ந்துவிடும், எல்லா மதிப்புகளும் நினைவகத்தில் இருக்கும்.

**list comprehension அல்லது append கொண்ட சுழல் எது வேகமானது?**

பொதுவாக comprehension சற்று வேகமானது, ஏனெனில் ஒவ்வொரு சுற்றிலும் append முறையை அழைப்பதில்லை. ஆனால் வாசிப்புத்திறனின் அடிப்படையில் தேர்ந்தெடுங்கள்; வேறுபாடு அரிதாகவே தீர்மானகரமானது.

**ஒரு பொருள் generator என்று எப்படி அறிவது?**

பொருளுக்கு inspect.isgenerator(obj), செயல்பாட்டுக்கு inspect.isgeneratorfunction(f). பொருள் iterator தானா என்று isinstance(obj, collections.abc.Iterator) மூலம் சரிபார்க்கலாம்.

**generator வெளிப்பாடும் list comprehension-உம் இடையே வேறுபாடு என்ன?**

சதுர அடைப்புகள் முழு பட்டியலையும் உடனே நினைவகத்தில் கட்டும்; சுற்று அடைப்புகள் கோரிக்கையின்போது உறுப்புகளைக் கணக்கிடும் generator-ஐ உருவாக்கும், அது ஒரு முறை மட்டுமே கடக்கப்படும்.


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