Functions
Decorators
Modifying function behavior with wrapper functions and @ syntax
Interview: Very important for interviews — used in web frameworks, caching, auth, and logging
Decorators are a powerful Python feature that allows you to modify or extend the behavior of functions and methods without permanently changing their code. They use the @decorator syntax and are widely used in web frameworks (Flask, Django), testing, caching, and access control.
How Decorators Work
- @ syntax is sugar:
@decoratorabove a function is equivalent tofunc = decorator(func) - Decorator is a function: It takes a function as input and returns a new (modified) function
- Wrapper function: The inner function that wraps the original, adding behavior before/after
- functools.wraps: ALWAYS use
@wraps(func)on the wrapper to preserve the original function's name, docstring, and annotations
Types of Decorators
- Simple decorator:
@timer— takes only the function as argument - Decorator with arguments:
@repeat(3)— needs an extra level of nesting (decorator factory) - Class method decorators:
@staticmethod,@classmethod,@property— built into Python - Class-based decorators: Use
__call__method — useful for stateful decorators
Stacking Decorators
Multiple decorators are applied bottom-up: @a @b @c is equivalent to func = a(b(c(func))). The closest decorator to the function runs first. Order matters — think about whether logging should wrap timing or vice versa.
Common Decorator Patterns
- Timer/profiler: Measure function execution time
- Cache/memoize: Store results to avoid recomputation —
@lru_cache - Auth/permission: Check user permissions before executing
- Logging: Log function calls, arguments, and return values
- Retry: Automatically retry failed operations with backoff
- Rate limiting: Restrict how often a function can be called
Common Pitfall: Forgetting @wraps
Without @wraps(func), the decorated function loses its __name__, __doc__, and __module__. This breaks help(), debugging, and some frameworks (Flask routes depend on function names).
Use Cases
Adding logging, timing, and profiling to functions without modifying their code
Implementing caching/memoization (functools.lru_cache is a built-in decorator)
Access control and authentication in web frameworks (Flask, Django)
Retry logic for flaky network operations and API calls
Input validation, rate limiting, and permission checks
Common Mistakes
Forgetting @wraps(func) — loses function name, docstring, and annotations
Not using `*args`, `**kwargs` in wrapper — breaks decorated functions with different signatures
Confusing decorator order with stacking — @a @b is a(b(func)), not b(a(func))
Forgetting the extra nesting level for parameterized decorators — @retry(3) needs 3 levels
Using decorators when a context manager (with statement) would be more appropriate