LearningPython TutorialIterators, Generators & Decorators
CHAPTER 18 · LAZY DATA & FUNCTION WRAPPERS

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.

English + Hinglish78 min readOnline practice included

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.

iter-basic.pyPython
skills = ["HTML", "CSS", "Python"]
iterator = iter(skills)

print(next(iterator))
print(next(iterator))
print(next(iterator))
Hinglish Explanation

iter(skills) list se iterator banata hai. Har next() call next item deta hai. Iterator same sequence position ko remember karta hai.

Try iterator basics →

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.

stop.pyPython
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

for-protocol.pyPython
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.

countdown.pyPython
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.

course.pyPython
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.

generator.pyPython
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.

Try a generator →

yield pauses and preserves state

state.pyPython
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

  • return function ko end karke one final value deta hai.
  • yield one 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.

lazy.pyPython
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.

gen-expression.pyPython
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

yield-from.pyPython
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

pipeline.pyPython
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

infinite.pyPython
def counter(start=1):
    while True:
        yield start
        start += 1

for number in counter():
    print(number)
    if number == 5:
        break
Important: Infinite generator ko list() 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.

first-decorator.pyPython
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.

Try a decorator →

Decorators for functions with arguments

flexible-wrapper.pyPython
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

wraps.pyPython
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.

repeat.pyPython
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

stacking.pyPython
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.
Design note: Decorator hidden control flow add karta hai. Simple direct function call enough ho to unnecessary decorator layer mat banao.

Simple timing decorator

timing.pyPython
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 wrapper

finally 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() and filter() — 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 par StopIteration ko 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, **kwargs forward na karna when needed.
  • functools.wraps skip 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 @wraps use 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() aur StopIteration samajh aaye?
  • Custom iterator ke __iter__/__next__ methods ka role clear hai?
  • yield generator state ko kaise preserve karta hai?
  • Generator expression aur list comprehension ka difference samajh aaya?
  • yield from ka basic use kar sakte ho?
  • Decorator syntax ka transformation model clear hai?
  • *args, **kwargs aur functools.wraps ke saath safe wrapper likh sakte ho?

Practice Task — Lazy Learning Activity Pipeline

BrounStack Playground me generator + decorator based mini pipeline banao.

  1. Raw activity strings ki list banao.
  2. clean_activities() generator banao jo whitespace remove kare and blanks skip kare.
  3. python_only() generator banao jo Python-related entries filter kare.
  4. Pipeline ko for loop se consume karo.
  5. Same generator ko second time consume karke behavior observe karo.
  6. Fresh generator recreate karke correct second pass dikhao.
  7. One generator expression banao.
  8. yield from se two topic lists combine karo.
  9. @log_call decorator banao.
  10. Wrapper me *args, **kwargs forward karo.
  11. @wraps add karo.
  12. Decorated function ka return value preserve karo.
  13. One configurable @repeat(2) decorator try karo.
  14. Comment me explain karo ki generator lazy kyu hai aur decorator kis behavior ko reuse karta hai.

Open practice starter →