Decorators
Decorators are a structural design pattern which wrap the original object, to add extra functionality dynamically, without modifying the original object. Decorators are usually used to run code before and/or after a function. Thus, they decorate the function with additional functionality, without changing the original function. They can also be used to alter the functionality of methods and classes.
Creating Decorators
Functions in Python are first class objects: they can be assigned to variables, passed into functions as arguments, returned from functions and declared inside functions.
With that in mind, a decorator can be created using functions or classes.
Decorator using Functions
The general syntax for creating a decorator using functions is as follows:
def decorator_name(original_function):def wrapper():# Code can be executed before the original function calloriginal_function()# Code can be executed after the original function callreturn wrapper
The original_function()
, in this case, is the function that is decorated with the decorator_name()
.
For example, a decorator that upper cases the returned values from any function can be created as follows:
# Define a function called upper_case_decorator() which will act as a decorator with parameter original_functiondef upper_case_decorator(original_function):# Creating the wrapper to wrap additional code around the original functiondef wrapper():# Code can be executed before the original functionprint(f"'{original_function.__name__}' function execution will start now.")# Original function call with the upper cased argumentresult = original_function(arg1)# Code can be executed after the original functionresult = result.upper()return result# Return the wrapper functionreturn wrapper
Decorator using Classes
The general syntax for creating a decorator using classes is as follows:
class decorator:def __init__(self, original_function):self.original_function = original_functiondef __call__(self):# Code can be executed before the original function# Original function call with the upper cased argumentself.original_function()# Code can be executed after the original function
For example, a decorator created using class based syntax that upper cases the return values from any function:
Applying Decorator to a Function
The decorators created above accept an argument called original_function
which can be any function. Therefore, we create a greeting()
function and decorate it with the @upper_case_decorator
.
def greeting():return "hello Dharma"
greeting()
function can be decorated with the common python syntax by calling the decorator function and assigning the result back to greeting
.
# Without using the @decorator syntaxgreeting = upper_case_decorator(greeting)print(greeting())# Output: HELLO DHARMA
However, Python provides a simpler way to achieve the same result using the @
symbol syntax. Add the line @upper_case_decorator
at the start of function definition to decorate it.
# Using the @decorator syntax@upper_case_decoratordef greeting():return 'hello Dharma'print(greeting())# Output: HELLO DHARMA
@upper_case_decorator
or @upper_case_decorator_with_class
can be used to decorate other functions as well.
@upper_case_decorator_with_classdef farewell():return 'bye'farewell()# Output: BYE
The full code with a decorator created using function will be:
Passing arguments to the Decorated functions
Arguments can be passed to the original function by creating parameters in the wrapper()
function.
Eg: arg1
is the parameter that the original function greeting()
requires. Thus, the wrapper()
function receives it and passes it along to the original function call.
General purpose decorators can be also be created that can accept any number of arguments by packing positional arguments using *args
and by packing keyword arguments using **kwargs
.
def general_decorator(original_function):# accepting variable number of argumentsdef wrapper(*args, **kwargs):func(args, kwargs)return wrapper@general_decoratordef original_function(args, kwargs):# code for the function
An example of variable arguments accepting decorator is as follows:
Passing Arguments to Decorators
Arguments can also be passed to the decorator itself. To pass arguments to the decorator, there should be a decorator_wrapper_to_accept_decorator_arguments()
function wrapping the original decorator.
def decorator_wrapper_to_accept_decorator_arguments(decorator_arg1, decorator_arg2):def decorator(original_function):def wrapper(*args, **kwargs):original_function(args, kwargs)return wrapper# (IMPORTANT)return decorator@decorator_wrapper_to_accept_decorator_arguments('value 1', 'value 2')def function_name(arg):# statements
Eg: With the greeting function, the decorator could be setup to use a number_of_times_to_greet
parameter to greet once or more than once.
def upper_case_decorator_wrapper(number_of_times_to_greet):def upper_case_decorator(original_function):def wrapper(*arg1, **kwargs):result = ""for i in range(number_of_times_to_greet):result += original_function(*arg1, **kwargs)result = result.upper()return resultreturn wrapperreturn upper_case_decorator@upper_case_decorator_wrapper(1)def greeting(arg1):return arg1 + "!"@upper_case_decorator_wrapper(2)def greeting_twice(arg1):return arg1 + "!"print(greeting('hi'))# output:# HI!print(greeting_twice('hi'))# output:# HI!HI!
Even though, greeting()
and greeting_twice()
have the exact same function definition, their outputs are different because the decorator is executing these functions as per the number_of_times_to_greet
argument passed to the decorator.
help()
with Decorators
The original function name and docstring are lost when it’s wrapped by a decorator.
The name of the function is greeting()
and not wrapper()
. The decorator completely hides the function and it’s docstring. This also means that the help(greeting)
call will be completely useless since it’ll have no information about the greeting()
function.
To solve this problem, python provides a decorator. @functools.wraps()
decorator can be used to decorate the wrapper()
function.
Example Decorators
Decorators can be used to measure the performance of a function
Decorators are used to create specialized functions
Special functions like ‘once’ that would only execute one time even if they’re called more than once, can be created using decorators.
Contribute to Docs
- Learn more about how to get involved.
- Edit this page on GitHub to fix an error or make an improvement.
- Submit feedback to let us know how we can improve Docs.