On this page
Decorators and Generators
Decorators
Decorators wrap functions to extend behavior without modifying the original:
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
@log_calls
def greet(name):
return f"Hello, {name}!"
greet("Alice")
Decorators with Arguments
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
Built-in Decorators
class User:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@staticmethod
def validate_email(email):
return "@" in email
@classmethod
def from_dict(cls, data):
return cls(data["name"])
Generators
Yield values lazily, saving memory:
def count_up_to(n):
i = 1
while i <= n:
yield i
i += 1
for num in count_up_to(5):
print(num) # 1, 2, 3, 4, 5
Generator Expressions
squares = (x**2 for x in range(1000000)) # memory-efficient
total = sum(x**2 for x in range(100))
Context Managers
Manage setup and teardown with with:
from contextlib import contextmanager
@contextmanager
def timer():
import time
start = time.perf_counter()
yield
elapsed = time.perf_counter() - start
print(f"Took {elapsed:.4f}s")
with timer():
sum(range(1_000_000))
Custom class-based context managers implement __enter__ and __exit__.
Itertools
from itertools import chain, islice, groupby
list(chain([1, 2], [3, 4])) # [1, 2, 3, 4]
list(islice(range(100), 5)) # [0, 1, 2, 3, 4]
These patterns appear throughout Python frameworks and are essential for writing idiomatic, efficient code.
functools.wraps
Preserve function metadata when wrapping:
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def add(a, b):
'''Add two numbers.'''
return a + b
print(add.__name__) # add (not wrapper)
Class-Based Decorators
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call #{self.count}")
return self.func(*args, **kwargs)
@CountCalls
def process():
return "done"
functools.lru_cache
Memoize expensive pure functions:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
Custom Context Manager Class
class DatabaseConnection:
def __enter__(self):
self.conn = connect_to_db()
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
self.conn.close()
return False # do not suppress exceptions
with DatabaseConnection() as db:
db.execute("SELECT 1")