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
Post a Comment