Function Decorators in Python
The description of function decorators in the language reference is quite dense, so I’m teasing it apart here with two examples from 5 Custom Python Decorators For Your Projects by NeuralNine.
Decorator without parameters
def time_logger(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print("Time taken:", end - start)
return result
return wrapper
@time_logger
def process_input(n):
# whatever
Decorator expressions are evaluated when the function is defined, in the scope that contains the function definition. The result must be a callable,
The result of the expression here is time_logger
, i.e. a reference to
the function time_logger
, not the result of that function.
which is invoked with the function object as the only argument.
Then time_logger(process_input)
is called, which returns an instance
of wrapper
closed over process_input
.
The returned value is bound to the function name instead of the function object.
Finally process_input
is assigned a reference to that instance of
wrapper, so any call to process_input
will invoke the wrapper instance
instead.
Decorator with parameters
def retry(retries=3, exception=Exception):
def decorator(func):
def wrapper(*args, **kwargs):
attempts = 0
while attempts < retries:
try:
return func(*args, **kwargs)
except exception as e:
attempts += 1
print(f"Failed ({attempts}/{retries})")
raise exception
return wrapper
return decorator
@retry(retries=5, exception=ValueError)
def error_prone_function():
# whatever
Decorator expressions are evaluated when the function is defined, in the scope that contains the function definition. The result must be a callable,
Here the decorator expression is a function call, not just the name of
the function, so the result of the expression is the return value of
retries(...)
, i.e. decorator
closed over retries=5
and
exception=ValueError
.
which is invoked with the function object as the only argument.
This will then be called with error_prone_function
as an argument,
which will return wrapper
and the rest is as before.
Decorators which can be used with or without parameters
Some decorators (e.g. functools.lru_cache) can be used with or without parameters.
In this case the function must check its parameters (if there is only one and its a callable then it was used without parameters), which is a bit of a hack.