அடிப்படை

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

புதுப்பிக்கப்பட்டது: · Skillaria.Top

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:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

print(list(Countdown(3)))  # [3, 2, 1]

இது வேலை செய்கிறது, ஆனால் நிறைய வார்ப்புரு குறியீடு. Generator-கள் அதே வேலையை மூன்று வரிகளில் செய்கின்றன.

Python generator என்றால் என்ன, yield எப்படி வேலை செய்கிறது

Generator செயல்பாடு என்பது உடலில் yield உள்ள சாதாரண செயல்பாடு. அதை அழைப்பது குறியீட்டை இயக்காது; generator பொருளைத் திருப்பும். ஒவ்வொரு next() அழைப்பும் உடலை அருகிலுள்ள yield வரை இயக்கி, மதிப்பைத் தந்து, செயல்பாட்டை அதன் உள்ளூர் மாறிகளுடன் உறைய வைக்கும். அடுத்த next() அதே இடத்திலிருந்து தொடரும்.

def countdown(start):
    while start > 0:
        yield start
        start -= 1

gen = countdown(3)
print(gen)          # <generator object countdown at 0x...>
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 ஆகியவை செலவு கண்காணிப்பானின் எடுத்துக்காட்டில் பாடநெறியில் நடைமுறையில் விளக்கப்பட்டுள்ளன.

அடிக்கடி கேட்கப்படும் கேள்விகள்

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

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

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

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

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

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

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

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

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

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

இந்தத் தலைப்பை வீடியோவில் கற்றுக்கொள்ளுங்கள்

“Python 3.15: от новичка до профи” பாடநெறியில் இந்தத் தலைப்பு பயிற்சியுடன் விரிவாக விளக்கப்பட்டுள்ளது.

பாடநெறிக்குச் செல்