Summary: in this tutorial, you’ll learn about Python decorators and how to develop your own decorators.
What is a decorator in Python
A decorator is a function that takes another function as an argument and extends its behavior without changing the original function explicitly.
Let’s take a simple example to understand the concept.
A simple Python decorator example
The following defines a net_price
function:
def net_price(price, tax):
""" calculate the net price from price and tax
Arguments:
price: the selling price
tax: value added tax or sale tax
Return
the net price
"""
return price * (1 + tax)
Code language: Python (python)
The net_price
function calculates the net price from selling price and tax. It returns the net_price
as a number.
Suppose that you need to format the net price using the USD currency. For example, 100
becomes $100
. To do it, you can use a decorator.
By definition, a decorator is a function that takes a function as an argument:
def currency(fn):
pass
Code language: Python (python)
And it returns another function:
def currency(fn):
def wrapper(*args, **kwargs):
fn(*args, **kwargs)
return wrapper
Code language: Python (python)
The currency
function returns the wrapper
function. The wrapper
function has the *args
and **kwargs
parameters.
These parameters allow you to call any fn
function with any combination of positional and keyword-only arguments.
In this example, the wrapper
function essentially executes the fn
function directly and doesn’t change any behavior of the fn
function.
In the wrapper
function, you can call the fn
function, get its result, and format the result as a currency string:
def currency(fn):
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
return f'${result}'
return wrapper
Code language: Python (python)
The currency
function is a decorator.
It accepts any function that returns a number and formats that number as a currency string.
To use the currency
decorator, you need to pass the net_price
function to it to get a new function and execute the new function as if it were the original function. For example:
net_price = currency(net_price)
print(net_price(100, 0.05))
Code language: Python (python)
Output:
$105.0
Python decorator definition
In general, a decorator is:
- A function that takes another function (original function) as an argument and returns another function (or closure)
- The closure typically accepts any combination of positional and keyword-only arguments.
- The closure function calls the original function using the arguments passed to the closure and returns the result of the function.
The inner function is a closure because it references the fn
argument from its enclosing scope or the decorator function.
The @ symbol
In the previous example, the currency is a decorator. And you can decorate the net_price
function using the following syntax:
net_price = currency(net_price)
Code language: Python (python)
Generally, if decorate
is a decorator function and you want to decorate another function fn
, you can use this syntax:
fn = decorate(fn)
Code language: Python (python)
To make it more convenient, Python provides a shorter way like this:
@decorate
def fn():
pass
Code language: Python (python)
For example, instead of using the following syntax:
net_price = currency(net_price)
Code language: Python (python)
… you can decorate the net_price
function using the @currency
as follows:
@currency
def net_price(price, tax):
""" calculate the net price from price and tax
Arguments:
price: the selling price
tax: value added tax or sale tax
Return
the net price
"""
return price * (1 + tax)
Code language: Python (python)
Put it all together.
def currency(fn):
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
return f'${result}'
return wrapper
@currency
def net_price(price, tax):
""" calculate the net price from price and tax
Arguments:
price: the selling price
tax: value added tax or sale tax
Return
the net price
"""
return price * (1 + tax)
print(net_price(100, 0.05))
Code language: Python (python)
Introspecting decorated functions
When you decorate a function:
@decorate
def fn(*args,**kwargs):
pass
Code language: Python (python)
It’s equivalent:
fn = decorate(fn)
Code language: Python (python)
The decorate
function returns a new function, which is the wrapper function.
If you use the built-in help
function to show the documentation of the new function, you won’t see the documentation of the original function. For example:
help(net_price)
Code language: Python (python)
Output:
wrapper(*args, **kwargs)
None
Also, if you check the name of the new function, Python will return the name of the inner function returned by the decorator:
print(net_price.__name__)
Code language: Python (python)
Output:
wrapper
So when you decorate a function, you’ll lose the original function signature and documentation.
To fix this, you can use the wraps
function from the functools
standard module. In fact, the wraps
function is also a decorator.
The following shows how to use the wraps
decorator:
from functools import wraps
def currency(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
return f'${result}'
return wrapper
@currency
def net_price(price, tax):
""" calculate the net price from price and tax
Arguments:
price: the selling price
tax: value added tax or sale tax
Return
the net price
"""
return price * (1 + tax)
help(net_price)
print(net_price.__name__)
Code language: Python (python)
Output:
net_price(price, tax)
calculate the net price from price and tax
Arguments:
price: the selling price
tax: value added tax or sale tax
Return
the net price
net_price
Code language: JavaScript (javascript)
Summary
- A decorator is a function that changes the behavior of another function without explicitly modifying it.
- Use the
@
symbol to decorate a function. - Use the
wraps
function from thefunctools
built-in module to retain the documentation and name of the original function.