Aujourd'hui nous allons voir les décorateurs par le code. Un peu de théorie avant.
Un décorateur est un mécanisme qui prend en paramètre une fonction et retourne une fonction. Le décorateur s'exécute au moment de la définition de la méthode et non lors de l'appel.
Par une simple annotation au-dessus de la méthode, nous mettons en place un décorateur
Copier le code
1 2 3 @deco def my_method(): ...
En faite python fait cela deco(my_method)
Voici un cas simple :
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def decorate(func): print("Function parameter {0}".format(func)) return func class Test: @decorate def get_text(self): print("Call get_text method !") custom = Test() custom.get_text() # Function parameter <function get_text at 0x0000000004C1DDD8> # Call get_text method !
Il est aussi possible de faire un décorateur avec une méthode à l'intérieur, ce que l'on appelle une closure
En passant les paramètres dynamiques (*args et **kwargs) cela permet de décorer une méthode qui possède des paramètres.
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 """ Test decorator with sub method """ def decorate(func): def func_custom(*args, **kwargs): print("Function parameter {0}".format(func)) return func(*args, **kwargs) return func_custom class Test: @decorate def get_text(self, content): print(f"Call get_text method with {content=}!") custom = Test() custom.get_text("Hello") # Function parameter <function get_text at 0x00000000047DDDD8> # Call get_text method with content='Hello'!
On peut aussi faire un décorateur sous forme de classe. La subtilité est d'utiliser la métaclasse __call__ pour exécuter la méthode.
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 """ Test decorator with class """ class Decorate: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("Call decorator class") return self.func(self, *args, **kwargs) class Test: @Decorate def get_text(self): print("Call get_text method !") custom = Test() custom.get_text() # Call decorator class # Call get_text method !
On peut envisager l'usage des multiples décorations. L'ordre est important car le 2ᵉ décorateur est injecté dans le 1ᵉʳ décorateur qui lui-même prend la méthode décorée
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 """ Test decorator with double decorator """ def decorate(func): def func_custom(*args, **kwargs): print("Function parameter {0}".format(func)) return func(*args, **kwargs) return func_custom def wrap(func): def func_custom(*args, **kwargs): print("*********************") func(*args, **kwargs) print("*********************") return func_custom class Test: @decorate @wrap def get_text(self): print("Call get_text method !") custom = Test() custom.get_text() # Function parameter <function func_custom at 0x000000000465DEB8> # ********************* # Call get_text method ! # *********************
Voilà la théorie exposée, maintenant à quoi cela sert-il ?
- Faire un wrapper
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 """ Test decorator with class """ class Decorate: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("Call decorator class") return self.func(self, *args, **kwargs) class Test: @Decorate def get_text(self): print("Call get_text method !") custom = Test() custom.get_text() # Call decorator class # Call get_text method !
- Mettre des logs
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 """ Test decorator with log decorator """ def decorate(text): def func_custom(func): print(text) return func return func_custom class Test: @decorate("Call get_text method") def get_text(self, *args, **kwargs): print("Inside method...") custom = Test() custom.get_text() # Call get_text method # Inside method...
- Déprécier une methode
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 """ Test decorator with deprecated decorator """ def deprecated(func): def func_custom(*args, **kwargs): print("The method {0} is deprecated".format(func.__name__)) return func(*args, **kwargs) return func_custom class Test: @deprecated def get_text(self, *args, **kwargs): print("Inside method...") custom = Test() custom.get_text() # The method get_text is deprecated # Inside method...
- Gérer les permisssions, validation des données ...
Si vous avez d'autres cas d'usages n'hésitez pas à les partager en commentaires ;-)