Python Decorators and Generators Explained

Part 12 of the Python for Everyone track. Last updated: September 2026.

Decorators and generators are Python's two "wow, that's elegant" features. A decorator wraps a function to add behavior; a generator produces values lazily, one at a time. Both appear constantly in real code — Flask routes, pytest fixtures, and data pipelines.

Decorators: functions that wrap functions

import functools, time

def timer(func):
    @functools.wraps(func)          # preserves the original name/docstring
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.3f}s")
        return result
    return wrapper

@timer                              # equivalent to: slow = timer(slow)
def slow():
    time.sleep(0.5)

slow()  # slow took 0.500s

Real-world decorators you'll meet: @app.route (Flask), @pytest.fixture, @lru_cache (memoization), @property, @staticmethod/@classmethod.

functools.lru_cache: memoization in one line

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

print(fib(100))   # instant — cached subproblems

Generators: lazy sequences with yield

def countdown(n):
    while n > 0:
        yield n          # pause here, hand n to the caller
        n -= 1

for x in countdown(3):
    print(x)             # 3, 2, 1

# Generator EXPRESSION — like a comprehension, but lazy
squares = (x*x for x in range(10**9))   # uses almost no memory
print(next(squares), next(squares))     # 0 1

A function with yield returns a generator object — nothing runs until you iterate. Perfect for large files, streams, and infinite sequences.

Decorators + generators together

def read_lines(path):
    with open(path) as f:
        for line in f:
            yield line.strip()      # lazy line-by-line reading

# Process a 10GB log without loading it into memory
for line in read_lines("server.log"):
    if "ERROR" in line:
        print(line)
        break

Key takeaways

  • A decorator is a function returning a wrapper — @name applies it; always use @functools.wraps.
  • yield turns a function into a generator — values produced lazily, memory stays flat.
  • Generator expressions (x for x in ...) are the lazy cousins of list comprehensions.
  • Combine them for clean, memory-efficient pipelines.

Next in this series: Testing Python Code with pytest: A Beginner's Guide.

Comments

Popular posts from this blog

Java Banking Finance Services and Insurance (BFSI) domain interview questions

JSP Servlet Interview Questions For Freshers Series 1

Java program to check even or odd number