# Python में जनरेटर: yield, इटरेटर और सूची व डिक्शनरी के जनरेटर

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

Python में जनरेटर एक ऑब्जेक्ट है, जो मान एक-एक करके और माँगे जाने पर देता है, पूरी श्रृंखला मेमोरी में नहीं रखता; इसे yield वाले फ़ंक्शन या कोष्ठक में लिखे जनरेटर व्यंजन से बनाया जाता है। सूची, डिक्शनरी और सेट के कॉम्प्रिहेंशन एक पंक्ति में तैयार संग्रह बनाने का छोटा तरीका हैं: [x * 2 for x in xs], {k: v for k, v in pairs}, {x for x in xs}।

Python में जनरेटर एक ऑब्जेक्ट है, जो मान एक-एक करके देता है, और तभी जब उससे माँगा जाए। पूरी श्रृंखला वह मेमोरी में नहीं रखता। इसे दो तरीकों से बनाया जाता है: `yield` कीवर्ड वाले फ़ंक्शन से, या कोष्ठक में लिखे जनरेटर व्यंजन से। इनके साथ सूची, डिक्शनरी और सेट के «जनरेटर» (comprehensions) भी हैं — एक पंक्ति में पूरा संग्रह तैयार करने का छोटा तरीका। यहाँ देखते हैं कि python yield कैसे काम करता है, जनरेटर इटरेटर से कैसे अलग है, और comprehension को अपठनीय बनने से कैसे बचाएँ।

## इटरेटर: for लूप की बुनियाद

जनरेटर समझने से पहले इटरेटर समझना ज़रूरी है। इटरेबल (iterable) वह चीज़ है जिस पर लूप चलाया जा सकता है: सूची, स्ट्रिंग, डिक्शनरी, फ़ाइल। इटरेटर वह ऑब्जेक्ट है जो अपनी स्थिति याद रखता है और `next()` कॉल होने पर अगला तत्व देता है; तत्व ख़त्म होने पर `StopIteration` उठाता है।

```
nums = [10, 20, 30]
it = iter(nums)      # सूची से इटरेटर लेना
print(next(it))      # 10
print(next(it))      # 20
print(next(it))      # 30
next(it)             # StopIteration
```

लूप `for` ठीक यही करता है: `iter()` बुलाता है, फिर `StopIteration` तक `next()`। अपना इटरेटर `__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` करता है), तो जनरेटर अपने-आप `StopIteration` उठाता है, और `for` लूप शांति से ख़त्म हो जाता है। यहीं `yield` और `return` का मुख्य फ़र्क है: `return` फ़ंक्शन ख़त्म करके एक मान लौटाता है, जबकि `yield` फ़ंक्शन को रोकता है और चाहे जितने मान दे सकता है।

### जनरेटर और इटरेटर में फ़र्क

हर जनरेटर एक इटरेटर है: उसमें `__iter__` और `__next__` होते हैं। पर हर इटरेटर जनरेटर नहीं होता। फ़र्क बनाने के तरीके और क्षमता में है।

|  | इटरेटर (क्लास) | जनरेटर |
| --- | --- | --- |

| कैसे बनता है | `__iter__` और `__next__` वाली क्लास | `yield` वाला फ़ंक्शन या `(x for x in ...)` |

| स्थिति | हाथ से एट्रिब्यूट में | लोकल वेरिएबल में अपने-आप सहेजी जाती है |

| StopIteration | आप ख़ुद उठाते हैं | फ़ंक्शन ख़त्म होने पर अपने-आप उठता है |

| अतिरिक्त | जो आप लिखें | `send()`, `throw()`, `close()` |

दोनों में एक अहम गुण समान है: उन पर एक ही बार चला जा सकता है। ख़त्म हो चुके जनरेटर पर दोबारा `for` कुछ नहीं देगा — नया बनाना होगा।

### अनंत जनरेटर और बड़ी फ़ाइलें

जनरेटर का ख़त्म होना ज़रूरी नहीं। अनंत श्रृंखला तब तक सुरक्षित है, जब तक उसमें से सीमित तत्व लिए जाएँ:

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

सबसे व्यावहारिक उदाहरण है बड़ी फ़ाइल को पंक्ति-दर-पंक्ति पढ़ना। जनरेटर को पाइपलाइन में जोड़ा जा सकता है, और मेमोरी में हर समय एक ही पंक्ति रहती है, पूरी फ़ाइल नहीं:

```
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` किसी दूसरे इटरेबल के सारे मान सीधे आगे भेजता है, और भीतरी लूप की ज़रूरत नहीं पड़ती:

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

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

`send()` जनरेटर के भीतर मान भेजता है: वह मान `yield` व्यंजन का परिणाम बन जाता है। पहले `send` से पहले जनरेटर को `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 जनरेटर है?

नहीं। `range` एक आलसी (lazy) अनुक्रम है: वह भी सारी संख्याएँ नहीं रखता, पर उसमें इंडेक्स, `len()`, `in` की जाँच है और उस पर कितनी भी बार चला जा सकता है। जनरेटर में यह कुछ भी नहीं है।

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

# वही कॉम्प्रिहेंशन से
with_markup = [a * 1.2 for a in amounts]
print(with_markup)  # [120.0, 300.0, 480.0]
```

नाम «सूची जनरेटर» के बावजूद परिणाम एक तैयार सूची है, जनरेटर नहीं: सारे तत्व तुरंत निकाल लिए जाते हैं।

### सूची जनरेटर में if और if else

यहीं सबसे ज़्यादा उलझन होती है। फ़िल्टर-शर्त (`if`) अंत में आती है और तत्व हटाती है। चयन-शर्त (टर्नरी) की जगह आरंभ में होती है और हर तत्व को दो में से एक मान बना देती है, कुछ नहीं हटाती।

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

द्वि-आयामी सूची (मैट्रिक्स) कॉम्प्रिहेंशन के भीतर कॉम्प्रिहेंशन से बनती है। सूची को गुणा करने की आम गलती पर ध्यान दें: पंक्तियाँ एक ही ऑब्जेक्ट बन जाती हैं।

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

# ट्रांसपोज़
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]]
```

## डिक्शनरी और सेट का जनरेटर

यही तरीका दूसरे संग्रहों पर भी चलता है। कोलन वाले कर्ली ब्रैकेट से डिक्शनरी बनती है (dict comprehension), और बिना कोलन के कर्ली ब्रैकेट से सेट (set comprehension)।

```
spent = [("खाना", 500), ("टैक्सी", 300), ("खाना", 200)]

categories = {cat for cat, _ in spent}
print(categories)  # {'खाना', 'टैक्सी'} — सेट का क्रम तय नहीं होता

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` लें।

### टपल का जनरेटर?

Python में अलग से «tuple comprehension» नहीं है। कोष्ठक से जनरेटर व्यंजन बनता है, और टपल पाने के लिए उसे `tuple()` में दिया जाता है:

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

## जनरेटर व्यंजन: जमा न करके एक-एक देना

सूची के चौकोर ब्रैकेट की जगह गोल कोष्ठक लिखें, तो जनरेटर व्यंजन बनता है। यह पहले से कुछ नहीं गिनता, और मान एक-एक करके देता है। दस लाख तत्वों पर मेमोरी का फ़र्क बहुत बड़ा है:

```
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))   # कुछ सौ बाइट
```

जनरेटर व्यंजन ज़्यादातर एग्रीगेट फ़ंक्शन के भीतर रहते हैं। अगर जनरेटर अकेला आर्गुमेंट हो, तो अतिरिक्त कोष्ठक ज़रूरी नहीं। `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()` चाहिए — तो सूची लें। एक बार चलकर कुछ गिनना है — जनरेटर लें।

## दायरा (scope): वेरिएबल बाहर नहीं निकलता

यह सवाल अनुभवी लोगों को भी उलझा देता है:

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

कॉम्प्रिहेंशन के भीतर लूप का वेरिएबल अपने दायरे में रहता है और बाहर के वेरिएबल को नहीं बदलता (Python 3 में ऐसा ही है; Python 2 में अलग था)। साधारण `for` लूप के बाद `x` 2 होता। Python 3.12 (PEP 709) से कॉम्प्रिहेंशन घेरने वाले फ़ंक्शन में समाहित हो गए और तेज़ चलते हैं, पर दायरे का नियम वही रहा।

## कॉम्प्रिहेंशन में * और ** की अनपैकिंग: Python 3.15 (PEP 798)

फ़ंक्शन कॉल और सूची-लिटरल में तारे से अनपैकिंग लंबे समय से लिखी जा सकती है, पर कॉम्प्रिहेंशन के भीतर यह `SyntaxError` था। Python 3.15 में यह रोक हट गई: `*तत्व` का मतलब है «यहाँ उसके सारे तत्व डालो»। सूचियों, सेटों और जनरेटरों में, अतुल्यकालिक (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}        # सेट
lazy = (*g for g in groups)        # आलसी जनरेटर
```

डबल तारा डिक्शनरी के लिए यही करता है। यह परतदार सेटिंग्स के लिए सुविधाजनक है — «डिफ़ॉल्ट → फ़ाइल → फ़्लैग»: हर अगली परत पिछली की कुंजियों को ढक देती है।

```
# 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` रखें।

## अतुल्यकालिक जनरेटर

अगर `async def` फ़ंक्शन में `yield` है, तो वह अतुल्यकालिक जनरेटर बनता है। उस पर `async for` चलता है, और भीतर नेटवर्क या डिस्क के लिए `await` किया जा सकता है। अतुल्यकालिक कॉम्प्रिहेंशन भी होते हैं।

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

## आम गलतियाँ

- **तीन मंज़िला कॉम्प्रिहेंशन।** दो `for`, दो `if` और टर्नरी एक पंक्ति में — एक हफ़्ते बाद कोई नहीं पढ़ पाएगा। पंक्ति में न समाए तो सामान्य लूप लिखें: PEP 8 पठनीयता को संक्षिप्तता से ऊपर रखता है।
- **जनरेटर पर दोबारा चलाना।** दूसरा `sum(gen)` 0 देगा, क्योंकि जनरेटर ख़त्म हो चुका है।
- **साइड-इफ़ेक्ट के लिए कॉम्प्रिहेंशन।** `[print(x) for x in xs]` बेकार `None` की सूची बनाता है। कार्रवाई के लिए `for` लूप लें।
- **जनरेटर व्यंजन से सूची की उम्मीद।** `(x for x in xs)[0]` — `TypeError`, जनरेटर में इंडेक्स नहीं होते।

## अभ्यास के लिए जनरेटर के सवाल

1. `fib()` जनरेटर लिखें, जो फ़िबोनाची संख्याएँ अनंत तक दे, और `islice` से पहले 10 लें।
2. एक पंक्ति में वह डिक्शनरी बनाएँ, जिसमें तीन अक्षरों से लंबे शब्द «शब्द → लंबाई» के रूप में हों।
3. एक लाख तक की सम संख्याओं के वर्गों का योग निकालें, बिना सूची बनाए।
4. जनरेटर `chunks(seq, n)` लिखें, जो सूची के `n` तत्वों के टुकड़े दे।

## निष्कर्ष

इटरेटर `next()` से एक-एक तत्व देता है, और जनरेटर उसे लिखने का सबसे सरल तरीका है: `yield` वाला फ़ंक्शन या कोष्ठक वाला व्यंजन। सूची, डिक्शनरी और सेट के जनरेटर एक पंक्ति में संग्रह बनाते हैं: फ़िल्टर `if` अंत में, चयन `if else` आरंभ में। मेमोरी बचानी हो या डेटा-प्रवाह को एक बार संसाधित करना हो, तो जनरेटर लें; परिणाम बार-बार चाहिए तो सूची। कॉम्प्रिहेंशन, जनरेटर और Python 3.15 की नई अनपैकिंग कोर्स में ख़र्च ट्रैकर के उदाहरण पर व्यवहार में सिखाए जाते हैं।

## FAQ

**क्या जनरेटर फ़ंक्शन में return इस्तेमाल किया जा सकता है?**

हाँ। बिना मान का return सिर्फ़ जनरेटर ख़त्म करता है, और मान वाला return उसे StopIteration अपवाद के value एट्रिब्यूट में डालता है; इसे yield from वाला व्यंजन प्राप्त करता है।

**जनरेटर को सूची में कैसे बदलें?**

उसे list() में दें: list(gen)। इसके बाद जनरेटर ख़त्म हो जाता है, और सारे मान मेमोरी में आ जाते हैं।

**list comprehension और append वाला लूप, कौन तेज़ है?**

आम तौर पर comprehension थोड़ा तेज़ है, क्योंकि हर चक्र में append मेथड नहीं बुलाता। पर चुनाव पठनीयता से करें; फ़र्क शायद ही कभी निर्णायक होता है।

**कैसे पता करें कि ऑब्जेक्ट जनरेटर है?**

ऑब्जेक्ट के लिए inspect.isgenerator(obj), और फ़ंक्शन के लिए inspect.isgeneratorfunction(f)। यह जाँचने के लिए कि ऑब्जेक्ट इटरेटर है, isinstance(obj, collections.abc.Iterator) लें।

**जनरेटर व्यंजन और list comprehension में क्या फ़र्क है?**

चौकोर ब्रैकेट तुरंत पूरी सूची मेमोरी में बनाता है, जबकि कोष्ठक जनरेटर बनाता है, जो मान माँगे जाने पर निकालता है और सिर्फ़ एक बार चलता है।


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