Python offers a way to modify behaviour of the function or class without modifying it’s source by using Decorators
Common use case include logging, authorization, authentication, performance measurement, caching, rate limiting, input validation, retry logic, method chaining etc
There are two types of decorators: those that accept parameters and those that do not
Let’s focus on the example where decorator only takes the function to be wrapped within decorator
def uppercase(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
If you would like to use this decorator on a given function just use @uppercase
@uppercase
def greet(name):
return f"Hello, {name}!"
print(greet("Karishma"))
A Decorator which accepts parameters needs to provide a decorator factory, which returns the actual decorator for a given function
def role_required(role): # <-- decorator factory taking argument
def decorator(fn): # <-- actual decorator taking the function
def wrapper(*args, **kwargs):
# your logic here
return fn(*args, **kwargs)
return wrapper
return decorator
Decorators can also take a class as an argument by using the dunder methods __init__ and __call__ to initialize and wrap the function
class CubeCalculator:
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
result = self.function(*args, **kwargs)
# additional logic
return result
@CubeCalculator
def get_cube(n):
print("Input number:", n)
return n * n * n
BONUS
Recently I was testing some capabilities of micro web application framework Flask along with Swagger, provided by library called flasgger which creates Swagger 2.0 API documentation for all your Flask views extracting specs from docstrings or referenced YAML files.
After adding a small decorator to one of the endpoints, I found that Swagger stopped listing it under /apidocs url.
Solution to it was using @wraps function from functools to inherit the metadata provided for Swagger discoverability. Without @wraps, the wrapper function replaces the original function’s metadata—such as name, doc, and other attributes with its own.
def role_required(role):
@wraps
def decorator(fn):
def wrapper(*args, **kwargs):
# your logic here
return fn(*args, **kwargs)
return wrapper
return decorator