Decorators
A syntactic sugar for wrapping a function or method with another function, modifying its behavior without altering its original code.
@decorator_name
def func_to_decorate():
passThis static page keeps the syntax and examples indexed for search, while the coding app handles interactive exploration and saved references.
What it does
Overview
A syntactic sugar for wrapping a function or method with another function, modifying its behavior without altering its original code.
Decorators in Python are a powerful and elegant way to extend or modify the behavior of functions or classes. Essentially, a decorator is a callable that takes another function as an argument, adds some functionality, and returns a new function (or the modified original). The `@ame` syntax is syntactic sugar for `ecorate = ame(ecorate)`. This means that the decorator function is executed *at definition time* of the decorated function. Common use cases for decorators include logging function calls, timing execution, enforcing authentication/authorization, caching results, or registering functions. A typical decorator involves an outer function that takes the function to be decorated, and an inner wrapper function that contains the added logic and calls the original function. Using `functools.wraps` inside the wrapper is a best practice to preserve the original function's metadata (like `__name__`, `__doc__`, `__module__`). Decorators promote the Don't Repeat Yourself (DRY) principle and improve code modularity by separating concerns. While powerful, overly complex decorators can make debugging challenging. The time complexity added by a decorator is typically negligible, usually O(1) per function call, plus the complexity of the added logic. Best practices include keeping decorators focused on a single responsibility, using `functools.wraps`, and ensuring they don't obscure the core logic of the decorated function.
Quick reference
Syntax
@decorator_name
def func_to_decorate():
pass
See it in practice
Examples
A simple logging decorator
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
@log_calls
def multiply(x, y):
return x * y
add(2, 3)
multiply(a=5, y=4)Calling add with args: (2, 3), kwargs: {} add returned: 5 Calling multiply with args: (), kwargs: {'a': 5, 'y': 4} multiply returned: 20
The `alls` decorator wraps `add` and `multiply`. Before and after each call, it prints information about the function, its arguments, and its return value, without modifying the core logic of `add` or `multiply`.
A decorator with arguments
import functools
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}!")
greet("Alice")Hello Alice! Hello Alice! Hello Alice!
Decorators can accept arguments by using an outer function that takes the arguments and returns the actual decorator function. Here, `greet` is called 3 times.
Using multiple decorators
import functools
def uppercase_result(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
def add_exclamation(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs) + '!'
return wrapper
@add_exclamation
@uppercase_result
def get_message(name):
return f"Hello {name}"
print(get_message("Bob"))HELLO BOB!
Decorators can be stacked. They are applied from bottom to top (closest to the function first). So `esult` runs first, then `xclamation`.
Debug faster
Common Errors
Forgetting `functools.wraps`
Cause: Without `functools.wraps`, the decorated function loses its original name, docstring, and other metadata, making debugging harder.
Fix: Always use `@functools.wraps(func)` on the inner wrapper function within your decorator.
def simple_decorator(func):
def wrapper(): # Missing @functools.wraps(func)
return func()
return wrapper
@simple_decorator
def my_func():
"""My docstring"""
pass
# print(my_func.__name__) # Would print 'wrapper' instead of 'my_func'Runtime support
Compatibility
Source: Python Docs
Common questions
Frequently Asked Questions
A syntactic sugar for wrapping a function or method with another function, modifying its behavior without altering its original code.
Forgetting `functools.wraps`: Always use `@functools.wraps(func)` on the inner wrapper function within your decorator.