Decorators and Generators¶
Decorators and generators are advanced Python features for modifying function behavior dynamically and generating sequences of values on demand with minimal memory consumption.
1. Decorators¶
A decorator is a callable that takes another function as an argument, extends or alters its behavior without modifying the original source code, and returns a modified function.
Writing @decorator_name above a function definition:
is shorthand for:
Creating a Decorator with functools.wraps¶
Always use @functools.wraps on the wrapper function so that the original function's name and docstring are preserved:
import time
import functools
def measure_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
duration = time.perf_counter() - start
print(f"Function {func.__name__} took {duration:.4f}s to run.")
return result
return wrapper
@measure_time
def compute_sum(n: int) -> int:
"""Calculates sum of numbers up to n."""
return sum(range(n))
print(compute_sum(1_000_000))
Built-in Decorator: @functools.lru_cache¶
The standard library provides @functools.lru_cache for automatic memoization (caching function return values based on input arguments):
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # 12586269025 (instant computation)
2. Generators and the yield Statement¶
A generator function is a function that contains one or more yield statements. When called, it does not execute its body immediately; instead, it returns a generator iterator object.
Each call to next(generator) resumes the function until it reaches the next yield statement.
Processing Large Files with Generators¶
Generators allow you to process very large files or continuous data streams line-by-line without loading the entire content into RAM: