Decorators¶
Decorator allow to modify the behaviour of a function.
Simple decorator¶
The “prototype” of a decorator is :
- Parameter : the function to be modified
- Return : another function
The returned might be the same function as the function to be modified.
def my_decorator(fonction):
"""This is our decorator : it displays a message before calling the
decorated function"""
def modified_function():
"""This is the function returned by the decorator.
It is actually a modified version of the original function,
which displays a message before calling the original function."""
print("Let's call {0}...".format(fonction))
return fonction()
return modified_function
@my_decorator
def hello():
print("Hello !")
This :
@decorator
def fonction(...):
...
…is the same as this :
def fonction(...):
...
fonction = decorator(fonction)
Using the decorator replaces the function by the function modified by the decorator function.
Decorator with parameters¶
This :
@decorator(parameter)
def fonction(...):
...
…is the same as this :
def fonction(...):
...
fonction = decorator(parameter)(fonction)
decorator(parameter)
is a function, which is applied to function
.
Example :
import time
def time_control(nb_secs):
"""Controls the time t taken by a function to execute.
if t > nb_secs => display an alert"""
def decorator(function_to_be_executed):
"""This is our decorator.
It is called during the definition of the function
to be decorated"""
def modified_function():
"""Function returned by the decorator.
Calculates the function execution time if the user
took more than nb_secs to press enter."""
start_time = time.time() # Before executing the function
returned_value = function_to_be_executed() # Run the function
end_time = time.time()
tps_execution = end_time - start_time
if tps_execution >= nb_secs:
print("Function {0} took {1} to run.".format( \
function_to_be_executed, tps_execution))
return returned_value
return modified_function
return decorator
So we have :
time_control(nb_sec)
- returns
decorator(function_to_be_executed)
- returns
modified_function()
- returns
This could be used like that :
@controler_temps(4)
def wait():
input("Press enter...")
Result :
>>> wait() # Pressing enter right now
Press enter...
>>> wait() # Waiting more than 4s to press enter
Press enter...
Function, <function wait at 0x00BA5810> took 4.14100003242 to run
Decorator on functions with parameters¶
Taking the previous example, we only have to apply the synthax for functions that take multiple arguments :
- unnamed parameters preceded with
*
- named parameters preceded with
**
That difference is applied to modified_function
:
import time
def time_control(nb_secs):
"""Controls the time t taken by a function to execute.
if t > nb_secs => display an alert"""
def decorator(function_to_be_executed):
"""This is our decorator.
It is called during the definition of the function
to be decorated"""
def modified_function(*unnamed_parameters, **named_parameters):
"""Function returned by the decorator.
Calculates the function execution time if the user
took more than nb_secs to press enter."""
start_time = time.time() # Before executing the function
# Run the function
returned_value = function_to_be_executed(*unnamed_parameters,
**named_parameters)
end_time = time.time()
tps_execution = end_time - start_time
if tps_execution >= nb_secs:
print("Function {0} took {1} to run.".format( \
function_to_be_executed, tps_execution))
return returned_value
return modified_function
return decorator
Decorators applied to classes definitions¶
>>> def decorateur(classe):
... print("Définition de la classe {0}".format(classe))
... return classe
...
>>> @decorateur
... class Test:
... pass
...
Définition de la classe <class '__main__.Test'>
>>>
Chaining Decorators¶
@decorateur1
@decorateur2
def fonction():
...
Example of decorator to check argument types¶
def controler_types(*a_args, **a_kwargs):
"""On attend en paramètres du décorateur les types souhaités. On
accepte une liste de paramètres indéterminés, étant donné que notre
fonction définie pourra être appelée avec un nombre variable de
paramètres et que chacun doit être contrôlé"""
def decorateur(fonction_a_executer):
"""Notre décorateur. Il doit renvoyer fonction_modifiee"""
def fonction_modifiee(*args, **kwargs):
"""Notre fonction modifiée. Elle se charge de contrôler
les types qu'on lui passe en paramètres"""
# La liste des paramètres attendus (a_args) doit être de même
# Longueur que celle reçue (args)
if len(a_args) != len(args):
raise TypeError("le nombre d'arguments attendu n'est " \
"pas égal au nombre reçu")
# On parcourt la liste des arguments reçus et non nommés
for i, arg in enumerate(args):
if a_args[i] is not type(args[i]):
raise TypeError("l'argument {0} n'est pas du type " \
"{1}".format(i, a_args[i]))
# On parcourt la liste des paramètres reçus et nommés
for cle in kwargs:
if cle not in a_kwargs:
raise TypeError("l'argument {0} n'a aucun type " \
"précisé".format(repr(cle)))
if a_kwargs[cle] is not type(kwargs[cle]):
raise TypeError("l'argument {0} n'est pas de type" \
"{1}".format(repr(cle), a_kwargs[cle]))
return fonction_a_executer(*args, **kwargs)
return fonction_modifiee
return decorateur
Decorators for getters / setters / deleters¶
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
The @property
decorator turns the x()
method into a “getter” for a read-only attribute with the same name, and it sets the docstring for voltage to “I’m the ‘x’ property.”
Once a roperty object has been created with @property
, we can redefine its getter
, setter
and deleter
methods for accessors to the parameter.