The decorator pattern in Python is a means to add functionality to a function without actually changing the code of the decorated function.
The pattern
The decorator pattern consists of
- Defining a decorator function
- calling the decorator with the decorated function as parameter
- and assigning the return value of this call to the decorated function
Decorator function
In its basic form, a decorator is a function that receives a function (decorated function) as parameter, defines an internal function which wraps this decorated function and then returns the wrapper function
Here is a basic example of the pattern :
from functools import wraps
#this is the function to be decorated
def decorated(a,b):
print ("I am a decorated function")
print ("There is nothing special about me")
print (f"my arguments are a= {a} and b={b}")
#START Decorator Pattern
#1- define a decorator function
def my_decorator(funct):
@wraps(funct) # see below for more on @wraps
def wrapper(*args,**kwargs):
# code to be executed before executing funct
print(f"before {funct.__name__}")
funct(args,kwargs)
# code to be executed after executing funct
print (f"after {funct.__name__}")
return wrapper
#2- call the decorator with the decorated function as parameter
#3- and assign the return of the call to the decorated function
decorated = my_decorator(decorated)
#END Decorator Pattern
#calling the decorated function
decorated ("one", b = "two")
This produces:
before decorated I am a decorated function There is nothing special about me my arguments are a= one and b=two after decorated
@decorator syntax
Steps 2 and 3 of the pattern can be shorthanded by using the @decorator syntax. Right before the definition of the function to be decorated, insert a line containing @ followed by the name of the decorator function.
#this line decorates decorated_1 with my_decorator
@my_decorator
def decorated_2(a,b):
print ("I am also a decorated function")
print ("but I use the @decorator syntax")
print (f"arguments are {a} and {b}")
decorated_2("hello","world")
This will produce:
before decorated_2
I am also a decorated function
but I use the @decorator syntax
arguments are hello and world
after decorated_2
Good uses for decorators are:
- Logging
- Timing a function
- IO
- Network communication
Using @wraps
The wrapper function of the decorator is usually itself decorated with the @wraps(funct) decorator. This prevents the decorated function from having its attribute __name__ changed to the name of the wrapper function