Decorators are functions that take another function as an argument and return a new function with extended or modified behavior. They are commonly used for logging, permission checks, caching, execution time, and more.
Decorators are implemented using closures or classes that implement the __call__ method. The classic syntax is:
# Simple decorator def simple_decorator(func): def wrapper(*args, **kwargs): print("Before calling the function") result = func(*args, **kwargs) print("After calling the function") return result return wrapper @simple_decorator def my_func(): print("Main function") my_func()
As a result, it executes like this:
Before calling the function
Main function
After calling the function
The main benefit is the encapsulation of repetitive logic outside the main business logic.
Nuances:
functools.wraps, the original function's name and its docstring will be lost.Question: "If I decorate a function with a decorator and then try to get the function's name via __name__, what will I see and how can I preserve the original name?"
Answer:
By default, the name changes to the name of the wrapper (usually wrapper). To preserve the original metadata, use functools.wraps:
import functools def dec(f): @functools.wraps(f) def wrapper(*args): return f(*args) return wrapper @dec def foo(): pass print(foo.__name__) # will output 'foo', not 'wrapper'
Story
In a large resource automation project, after wrapping functions with decorators, the system of automated tests using reflection on test function names broke. The problem was the absence of functools.wraps.
Story
A logging decorator did not support functions with different signatures because *args, **kwargs were not used. Some functions simply failed with Silent Fail.
Story
In a project with authorization in the REST API, a developer implemented a decorator with parameters, but forgot to nest the functions correctly (there was double nesting instead of three). As a result, the decorator could not accept parameters.