Python Decorators - Using Decorator Functions in Python
Python Decorators - Using Decorator Functions in Python
Python decorators
last modified July 6, 2020
Python functions are first-class citizens. This means that functions have equal status with other
objects in Python. Functions can be assigned to variables, stored in collections, created and deleted
dynamically, or passed as arguments.
A nested function, also called an inner function, is a function defined inside another function.
Python decorator
Python decorator extends and modifies the behavior of a callable without modifying the callable
itself. Decorators are functions which decorate (or wrap) other functions and execute code before
and after the wrapped function runs.
Python decorators are often used in logging, authentication and authorization, timing, and caching.
enclose.py
#!/usr/bin/env python
def enclose(fun):
def wrapper():
print("***************************")
fun()
print("***************************")
return wrapper
def myfun():
print("myfun")
enc = enclose(myfun)
enc()
The enclose() function is a decorator which extends the decorated function by adding star
symbols to its output.
def enclose(fun):
...
def wrapper():
print("***************************")
fun()
print("***************************")
return wrapper
Google Ads -
Sitio O cial
Con Google Ads, no hay
contratos ni mínimo de
Google Ads inversión.
The wrapper() decorates the passed function with stars. The wrapper function is returned.
def myfun():
print("myfun")
enc = enclose(myfun)
enc()
The myfun is passed to the enclose() function, in which it is extended. The wrapper function is
returned and called.
$ ./enclose.py
***************************
myfun
***************************
This is the output. The decorator adds the stars before and after the output of the regular function.
enclose2.py
#!/usr/bin/env python
def enclose(fun):
def wrapper():
print("***************************")
fun()
print("***************************")
return wrapper
@enclose
def myfun():
print("myfun")
myfun()
Functionally, the example is equivalent to the previous one. Only different syntax is used.
param.py
#!/usr/bin/env python
def enclose(fun):
def wrapper(val):
print("***************************")
fun(val)
print("***************************")
return wrapper
@enclose
def myfun(val):
print(f"myfun with {val}")
myfun('falcon')
params.py
#!/usr/bin/env python
def enclose(fun):
print("***************************")
fun(*args, **kwargs)
print("***************************")
return wrapper
@enclose
def myfun(name, age):
print(f'{name} is {age} years old')
myfun(name='Peter', age=32)
myfun('Roman', 29)
This example shows how to deal with variable number of parameters using the *args, **kwargs
syntax.
Python decorator modify data
The decorator function can modify the data of the decorated function.
uppercase.py
#!/usr/bin/env python
def uppercase(fun):
def wrapper():
res = fun()
modified = res.upper()
return modified
return wrapper
@uppercase
def gen_message():
return 'Hello there!'
msg = gen_message()
print(msg)
The @uppercase decorator changes the returned text to uppercase.
def uppercase(fun):
def wrapper():
res = fun()
modified = res.upper()
return modified
return wrapper
$ ./uppercase.py
HELLO THERE!
def strong(fun):
def wrapper():
return f'<strong>{fun()}</strong>'
return wrapper
def em(fun):
def wrapper():
return f'<em>{fun()}</em>'
return wrapper
@strong
@em
def message():
return 'This is some message'
print(message())
$ ./multiple_decors.py
<strong><em>This is some message</em></strong>
timing.py
#!/usr/bin/env python
import time
import math
def timer(func):
begin = time.time()
f = func(*args, **kwargs)
end = time.time()
print("Total time taken in : ", func.__name__, end - begin)
return f
return wrapper
@timer
def factorial(num):
return math.factorial(num)
f = factorial(4580)
print(f)
The example calculates how long the factorial() function runs using a decorator.
begin = time.time()
end = time.time()
print("Total time taken in : ", func.__name__, end - begin)
After the function is run, we get the end time and print the difference.
The functools @wraps decorator
After applying the decorator function, the __name__, __doc__, and __module__ attributes of the
original function are lost. This makes debugging awkward. To fix this, we can use the functool's
@wraps decorator.
naming.py
#!/usr/bin/env python
def enclose(fun):
@wraps(fun)
def wrapper():
'''This is wrapper function'''
print("***************************")
fun()
print("***************************")
return wrapper
@enclose
def myfun():
'''this is myfun()'''
print("myfun")
myfun()
print(myfun.__name__)
print(myfun.__doc__)
In the example, we apply the @wraps decorator on the wrapper function. The name and the
docstring of the original function (myfun) are kept.
$ ./naming.py
***************************
myfun
***************************
myfun
this is myfun()
counting_calls.py
#!/usr/bin/env python
import functools
class CountCalls:
self.num_of_calls += 1
print(f"Call {self.num_of_calls} of {self.fun.__name__} fun")
return self.fun(*args, **kwargs)
@CountCalls
def hello():
print("Hello there!")
hello()
hello()
hello()
In the example, we use a class decorator to count the calls of a regular function.
functools.update_wrapper(self, fun)
self.fun = fun
self.num_of_calls = 0
We call the update_wrapper() function. It has the same purpose as the @wraps decorator; i.e. it
keeps the metadata of the original function (__name__ or __doc__). We keep the reference to the
original function and set the num_of_calls variable.
self.num_of_calls += 1
print(f"Call {self.num_of_calls} of {self.fun.__name__} fun")
return self.fun(*args, **kwargs)
We increase the num_of_calls variable, print a message, and call the original function, passing it
possible arguments.
$ ./counting_calls.py
Call 1 of hello fun
Hello there!
Call 2 of hello fun
Hello there!
Call 3 of hello fun
Hello there!
static_method.py
#!/usr/bin/env python
class Math:
@staticmethod
def abs(x):
if x < 0:
return -x
return x
print(Math.abs(3))
print(Math.abs(-3))
In the example, we create a static abs() method using the @staticmethod decorator. The method
is called by specifying the class name and using the dot operator: Math.abs().
Flask decorators
Popular Python framework Flask uses decorators. For instance, the @app.route() is used to
define routes.
app.py
#!/usr/bin/env python
@app.route('/')
def hello():
return 'Hello there!'
In the example, the hello() function is mapped to the root page using Flask's @app.route()
decorator.