Iterators और Generators
Iterators और generators क्यों?
Python का हर for loop एक iterator इस्तेमाल करता है – ऐसा object जो एक बार में एक item देता है। Generator अपना iterator बनाने का सबसे आसान तरीका है: एक function जो yield से values एक-एक करके, सिर्फ़ ज़रूरत पड़ने पर बनाता है।
यह बड़े data में बहुत काम आता है: 5 GB की log file पढ़ना, लाखों database rows stream करना, या अनंत series बनाना। List सब कुछ memory में ले आती है; generator सिर्फ़ मौजूदा item रखता है।
for loop असल में कैसे चलता है: iter() और next()
marks = [78, 91, 66]
it = iter(marks) # ask the list for an iterator
print(next(it))
print(next(it))
print(next(it))
try:
next(it) # nothing left
except StopIteration:
print("StopIteration – the loop ends here")for loop बस एक बार iter() बुलाता है, फिर बार-बार next(), और StopIteration आने पर चुपचाप रुक जाता है।
| शब्द | अर्थ | उदाहरण |
|---|---|---|
| Iterable | जिस पर loop चल सके; iter(x) काम करे | list, tuple, str, dict, set, file, range |
| Iterator | __next__ वाला object; अपनी जगह याद रखता है; एक बार इस्तेमाल | iter(list) का नतीजा, file objects, generators |
| Generator | yield वाले function या generator expression से बना iterator | def count(): yield 1, (x*x for x in data) |
Iterator class लिखना (लंबा तरीका)
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
for n in Countdown(3):
print(n, end=" ")
print("🚀")यह चलता है, पर state आपको ख़ुद संभालनी पड़ती है। Generator यही काम चार lines में करता है।
yield वाले generator functions
def countdown(start):
print("generator started")
while start > 0:
yield start # pause here and hand out a value
start -= 1
print("generator finished")
gen = countdown(3)
print(type(gen).__name__) # nothing has run yet
print(next(gen))
print(next(gen))
print(list(gen)) # takes the restcountdown(3)बुलाने पर कोई code नहीं चलता – सिर्फ़ generator object बनता है।- हर
next()अगलेyieldतक चलता है, वह value लौटाकर रुक जाता है, और सारे local variables याद रखता है। - Function ख़त्म होने पर
StopIterationअपने-आप आता है।
| return | yield |
|---|---|
| Function ख़त्म करता है | Function को रोकता है |
| एक value देता है | समय के साथ कई values दे सकता है |
| Local variables खो जाते हैं | Local variables बने रहते हैं |
Memory: list बनाम generator
import sys
squares_list = [n * n for n in range(1_000_000)]
squares_gen = (n * n for n in range(1_000_000)) # round brackets
print("list size :", sys.getsizeof(squares_list) // 1024, "KB")
print("gen size :", sys.getsizeof(squares_gen), "bytes")
print("same total:", sum(squares_list) == sum(squares_gen))List comprehension के square brackets को round brackets से बदलें, generator बन जाएगा। Function call के अंदर extra brackets छोड़ सकते हैं: sum(x*x for x in data)।
ख़त्म होने के बाद वह ख़ाली रहता है। Data दो बार चाहिए तो नया generator बनाएँ या list में रख लें।
असली उदाहरण: बड़ी log file पर data pipeline
Generators को pipes की तरह जोड़ सकते हैं। हर stage एक बार में एक line लेता है, इसलिए यह program 10 lines की file हो या 10 करोड़ lines की, उतनी ही कम memory लेता है।
def read_lines(path):
with open(path, encoding="utf-8") as f:
for line in f: # files are iterators too
yield line.rstrip("\n")
def only_errors(lines):
for line in lines:
if " ERROR " in line:
yield line
def parse(lines):
for line in lines:
date, time, level, *message = line.split()
yield {"time": time, "message": " ".join(message)}
errors = parse(only_errors(read_lines("server.log")))
for e in errors:
print(e["time"], "-", e["message"])अनंत generators और itertools
import itertools
def invoice_numbers(prefix="INV"):
n = 1
while True: # never ends – that is fine for a generator
yield f"{prefix}-{n:04d}"
n += 1
numbers = invoice_numbers()
print(next(numbers), next(numbers))
# take only what you need with islice
print(list(itertools.islice(invoice_numbers("SS"), 3)))
# batches of 2 students (itertools.batched exists from Python 3.12; this works everywhere)
def batches(items, size):
it = iter(items)
while chunk := list(itertools.islice(it, size)):
yield chunk
print(list(batches(["Asha", "Ravi", "Neha", "Kabir", "Isha"], 2)))yield from: दूसरे generator को काम सौंपना
def walk(folder):
"""Yield every file name inside nested folders (a dict tree)."""
for name, value in folder.items():
if isinstance(value, dict):
yield from walk(value) # hand over to the inner generator
else:
yield name
project = {"index.html": 1, "css": {"style.css": 1}, "js": {"app.js": 1, "lib": {"chart.js": 1}}}
print(list(walk(project)))आम ग़लतियाँ
| ग़लती | नतीजा | सुधार |
|---|---|---|
| एक ही generator पर दो बार loop | दूसरा loop कुछ नहीं छापता | दोबारा बनाएँ या list इस्तेमाल करें |
Generator पर len() | TypeError | sum(1 for _ in gen) से गिनें या list लें |
| Generator बनाते ही code चलने की उम्मीद | next()/loop तक कुछ नहीं होता | याद रखें: generators lazy होते हैं |
| बड़ी file के लिए list | MemoryError / धीमा program | File पर iterate करें या lines yield करें |
अभ्यास
even_numbers(limit)लिखें जो limit तक even numbers yield करे।marks.csvको line-by-line पढ़कर सिर्फ़ 75 से ज़्यादा marks वाले students yield करने वाला generator लिखें।- आज से शुरू होने वाला dates का अनंत generator बनाएँ (
datetime.dateऔरtimedelta) और अगले 7 दिन छापें। sys.getsizeofसे[x for x in range(10**6)]और(x for x in range(10**6))की memory तुलना करें।
सारांश
- Iterable → loop हो सकता है; iterator →
next()से items देता है और अपनी जगह याद रखता है। yieldवाला function generator लौटाता है; यह lazy चलता है और हर yield पर रुकता है।- Generator expressions
( … )lists[ … ]से कम memory लेते हैं। - Generators जोड़कर कम memory वाली pipelines बनाएँ; slicing और जोड़ने के लिए
itertools। - Generator एक बार ही इस्तेमाल होता है।
