Python Iterators, Generators & Decorators
Python loops ke peeche iteration protocol kaam karta hai, generators values ko lazily produce karte hain, aur decorators functions/classes ko wrap karke reusable behavior add karte hain. Is chapter me in teen concepts ko zero se practical patterns tak samjhenge.
Iterable vs iterator
Iterable wo object hai jisse iterator ban sakta hai, jaise list, tuple, string, set aur dictionary. Iterator next value produce karta hai and apni current progress remember karta hai.
skills = ["HTML", "CSS", "Python"]
iterator = iter(skills)
print(next(iterator))
print(next(iterator))
print(next(iterator))iter(skills) list se iterator banata hai. Har next() call next item deta hai. Iterator same sequence position ko remember karta hai.
StopIteration
Iterator ke items finish hone ke baad next() normally StopIteration raise karta hai. for loop internally is signal ko handle karke iteration stop kar deta hai.
it = iter([10, 20])
print(next(it))
print(next(it))
try:
print(next(it))
except StopIteration:
print("No more values")What a for loop roughly does
items = [1, 2, 3]
iterator = iter(items)
while True:
try:
item = next(iterator)
except StopIteration:
break
print(item)Real Python implementation optimized hoti hai, but ye model iteration protocol ko understand karne ke liye useful hai.
Create a custom iterator class
An iterator object normally __iter__() and __next__() implement karta hai.
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 number in Countdown(3):
print(number)Iterator stateful hota hai. Ek baar consume hone ke baad same iterator reset automatically nahi hota.
Iterable class with fresh iterators
Container-like object ko repeatable banana ho to __iter__() fresh iterator return kar sakta hai.
class Course:
def __init__(self, students):
self.students = students
def __iter__(self):
return iter(self.students)
course = Course(["Aman", "Riya"])
print(list(course))
print(list(course))Yaha each iteration underlying list ka new iterator create karti hai, so course ko repeatably iterate kar sakte hain.
What is a generator function?
Function body me yield use hone par calling it immediately full result compute nahi karti; generator object return hota hai. Har resume par generator next yielded value produce karta hai.
def countdown(start):
while start > 0:
yield start
start -= 1
for number in countdown(3):
print(number)Generator function automatically iterator protocol support karta hai, so custom __iter__/__next__ class likhne ki zaroorat nahi.
yield pauses and preserves state
def demo():
print("before first yield")
yield 10
print("before second yield")
yield 20
it = demo()
print(next(it))
print(next(it))Function yield par pause hota hai; local variables and execution position preserved rehte hain. Next request par wahi se resume hota hai.
return vs yield
returnfunction ko end karke one final value deta hai.yieldone value produce karke function ko pause karta hai.- Generator multiple values over time produce kar sakta hai.
- Generator ka normal completion iteration ko stop karta hai.
Lazy evaluation
Generators values ko demand par produce karte hain. Isse large/infinite streams ko all-at-once memory me store karna required nahi hota.
def squares(limit):
for number in range(limit):
yield number * number
for value in squares(1_000_000):
if value > 100:
break
print(value)Only required values produce hue; full million-item result list build nahi hui.
Generator expressions
List comprehension ke square brackets ki jagah parentheses use karke generator expression bana sakte ho.
squares = (number * number for number in range(5))
print(squares)
print(list(squares))Generator expression lazy hota hai. Once consumed, same generator se values dobara automatically available nahi hoti.
List comprehension vs generator expression
- List comprehension result immediately list me materialize karti hai.
- Generator expression values lazily produce karta hai.
- Small reusable result chahiye to list convenient hai.
- Large stream, pipeline ya one-pass processing me generator memory-friendly ho sakta hai.
Delegate with yield from
def all_topics():
yield from ["HTML", "CSS"]
yield from ["JavaScript", "Python"]
print(list(all_topics()))yield from iterable another iterable/generator ke values ko directly delegate karta hai.
Generator pipelines
def cleaned(lines):
for line in lines:
text = line.strip()
if text:
yield text
def long_names(names):
for name in names:
if len(name) >= 5:
yield name
raw = [" Aman ", "", "Riya", " Kabir "]
print(list(long_names(cleaned(raw))))Each stage values stream karta hai. Real applications me file processing and data transformation ke liye ye pattern useful hai.
Infinite generators need a stopping condition
def counter(start=1):
while True:
yield start
start += 1
for number in counter():
print(number)
if number == 5:
breaklist() me convert mat karo; program unbounded memory/time consume kar sakta hai.What is a decorator?
Decorator ek callable leta hai aur usually wrapped/replacement callable return karta hai. @decorator syntax function definition ko transform karne ka readable form hai.
def announce(func):
def wrapper():
print("Starting")
result = func()
print("Finished")
return result
return wrapper
@announce
def learn():
print("Learning Python")
learn()@announce roughly learn = announce(learn) ke equivalent hai.
Decorators for functions with arguments
def log_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def add(a, b):
return a + b
print(add(3, 4))*args and **kwargs wrapper ko different function signatures forward karne me help karte hain.
Preserve metadata with functools.wraps
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def greet(name):
"""Return a greeting."""
return f"Hello, {name}"
print(greet.__name__)
print(greet.__doc__)@wraps(func) original function ka name, docstring aur useful metadata preserve karta hai. Practical decorators me ise default habit bana sakte ho.
Decorator with its own arguments
Decorator configuration chahiye ho to extra outer function use hota hai.
from functools import wraps
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def hello():
print("Hello")
hello()Stack multiple decorators
def first(func):
def wrapper():
print("first before")
return func()
return wrapper
def second(func):
def wrapper():
print("second before")
return func()
return wrapper
@first
@second
def run():
print("run")
run()Decorators bottom-up apply hote hain: yaha roughly run = first(second(run)).
Practical decorator use cases
- Logging and tracing.
- Timing/metrics collection.
- Authorization checks.
- Caching.
- Retry policies with careful limits.
- Input/output validation around clear boundaries.
- Framework route/command registration.
Simple timing decorator
from functools import wraps
from time import perf_counter
def timed(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = perf_counter()
try:
return func(*args, **kwargs)
finally:
elapsed = perf_counter() - start
print(f"{func.__name__}: {elapsed:.6f}s")
return wrapperfinally timing output ko exception ke case me bhi run karne deta hai. Production metrics systems me logging/telemetry framework use kiya jata hai.
Useful iterator tools
Python built-ins aur Standard Library me iterator-friendly tools milte hain:
enumerate()— index + value.zip()— multiple iterables combine.map()andfilter()— lazy transformations in Python 3.reversed()— reverse iterator where supported.itertools— chaining, slicing, combinations and more advanced iterator utilities.
Chapter 19 me comprehensions, lambda, map/filter aur useful built-ins ko detail me compare karenge.
Common beginner mistakes
- Iterable aur iterator ko same thing samajhna.
- Consumed generator ko automatically reusable expect karna.
next()exhaustion parStopIterationko unexpected crash samajhna.- Infinite generator ko
list()me convert kar dena. - Generator use karke later same data repeatedly access karna without materializing/recreating it.
- Decorator wrapper me original return value forget karna.
- Wrapper me
*args, **kwargsforward na karna when needed. functools.wrapsskip karke metadata lose karna.- Decorators stack karte waqt application order misunderstand karna.
- Simple logic ko unnecessary decorator/generator abstraction me hide karna.
Beginner best practices
- Simple loops se start karo; iterator internals tab implement karo jab custom behavior required ho.
- Lazy processing genuinely useful ho tab generator choose karo.
- One-pass nature ko clearly understand/document karo.
- Generators me clear termination condition rakho unless intentionally infinite stream ho.
- Decorators ko one focused responsibility do.
- Practical wrappers me
@wrapsuse karo. - Original function arguments and return value preserve karo unless decorator contract intentionally different ho.
- Hidden side effects minimal rakho.
Chapter checklist
- Iterable aur iterator ka difference clear hai?
iter(),next()aurStopIterationsamajh aaye?- Custom iterator ke
__iter__/__next__methods ka role clear hai? yieldgenerator state ko kaise preserve karta hai?- Generator expression aur list comprehension ka difference samajh aaya?
yield fromka basic use kar sakte ho?- Decorator syntax ka transformation model clear hai?
*args,**kwargsaurfunctools.wrapske saath safe wrapper likh sakte ho?
Practice Task — Lazy Learning Activity Pipeline
BrounStack Playground me generator + decorator based mini pipeline banao.
- Raw activity strings ki list banao.
clean_activities()generator banao jo whitespace remove kare and blanks skip kare.python_only()generator banao jo Python-related entries filter kare.- Pipeline ko
forloop se consume karo. - Same generator ko second time consume karke behavior observe karo.
- Fresh generator recreate karke correct second pass dikhao.
- One generator expression banao.
yield fromse two topic lists combine karo.@log_calldecorator banao.- Wrapper me
*args, **kwargsforward karo. @wrapsadd karo.- Decorated function ka return value preserve karo.
- One configurable
@repeat(2)decorator try karo. - Comment me explain karo ki generator lazy kyu hai aur decorator kis behavior ko reuse karta hai.