Un décorateur prend une fonction, ajoute des fonctionnalités et la renvoie. Dans ce didacticiel, vous apprendrez comment créer un décorateur et pourquoi vous devriez l'utiliser.
Décorateurs en Python
Python a une fonctionnalité intéressante appelée décorateurs pour ajouter des fonctionnalités à un code existant.
Ceci est également appelé métaprogrammation car une partie du programme essaie de modifier une autre partie du programme au moment de la compilation.
Prérequis pour l'apprentissage des décorateurs
Afin de comprendre les décorateurs, nous devons d'abord connaître quelques notions de base en Python.
Nous devons être à l'aise avec le fait que tout en Python (oui! Même les classes), sont des objets. Les noms que nous définissons sont simplement des identifiants liés à ces objets. Les fonctions ne font pas exception, ce sont aussi des objets (avec des attributs). Différents noms différents peuvent être liés au même objet fonction.
Voici un exemple.
def first(msg): print(msg) first("Hello") second = first second("Hello")
Production
Bonjour bonjour
Lorsque vous exécutez le code, les deux fonctions first
et second
donnent le même résultat. Ici, les noms first
et second
font référence au même objet fonction.
Maintenant, les choses commencent à devenir plus bizarres.
Les fonctions peuvent être passées comme arguments à une autre fonction.
Si vous avez utilisé des fonctions comme map
, filter
et reduce
en Python, vous le savez déjà.
De telles fonctions qui prennent d'autres fonctions comme arguments sont également appelées fonctions d'ordre supérieur . Voici un exemple d'une telle fonction.
def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result
Nous invoquons la fonction comme suit.
>>> operate(inc,3) 4 >>> operate(dec,3) 2
De plus, une fonction peut renvoyer une autre fonction.
def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()
Production
Bonjour
Voici is_returned()
une fonction imbriquée qui est définie et renvoyée à chaque appel is_called()
.
Enfin, nous devons connaître les fermetures en Python.
Revenir aux décorateurs
Les fonctions et méthodes sont appelées comme appelables .
En fait, tout objet qui implémente la __call__()
méthode spéciale est appelé appelable. Ainsi, dans le sens le plus basique, un décorateur est un appelable qui renvoie un appelable.
Fondamentalement, un décorateur prend une fonction, ajoute des fonctionnalités et la renvoie.
def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")
Lorsque vous exécutez les codes suivants dans le shell,
>>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary
Dans l'exemple ci-dessus, make_pretty()
est un décorateur. Dans l'étape d'affectation:
pretty = make_pretty(ordinary)
La fonction a ordinary()
été décorée et la fonction renvoyée a reçu le nom pretty
.
Nous pouvons voir que la fonction décorateur a ajouté de nouvelles fonctionnalités à la fonction d'origine. Cela revient à emballer un cadeau. Le décorateur agit comme un emballage. La nature de l'objet décoré (cadeau réel à l'intérieur) ne change pas. Mais maintenant, il a l'air joli (depuis qu'il a été décoré).
Généralement, nous décorons une fonction et la réaffectons comme suit:
ordinary = make_pretty(ordinary).
C'est une construction courante et pour cette raison, Python a une syntaxe pour simplifier cela.
Nous pouvons utiliser le @
symbole avec le nom de la fonction décoratrice et le placer au-dessus de la définition de la fonction à décorer. Par exemple,
@make_pretty def ordinary(): print("I am ordinary")
est équivalent à
def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)
Ceci est juste un sucre syntaxique pour mettre en œuvre des décorateurs.
Décorer des fonctions avec des paramètres
Le décorateur ci-dessus était simple et il ne fonctionnait qu'avec des fonctions qui n'avaient aucun paramètre. Et si nous avions des fonctions qui acceptaient des paramètres comme:
def divide(a, b): return a/b
Cette fonction a deux paramètres, a et b. Nous savons que cela donnera une erreur si nous passons dans b comme 0.
>>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero
Faisons maintenant un décorateur pour vérifier ce cas qui causera l'erreur.
def smart_divide(func): def inner(a, b): print("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)
Cette nouvelle implémentation reviendra None
si la condition d'erreur se produit.
>>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide
De cette manière, nous pouvons décorer des fonctions qui prennent des paramètres.
Un observateur averti remarquera que les paramètres de la inner()
fonction imbriquée à l'intérieur du décorateur sont les mêmes que les paramètres des fonctions qu'il décore. En tenant compte de cela, nous pouvons maintenant créer des décorateurs généraux qui fonctionnent avec n'importe quel nombre de paramètres.
In Python, this magic is done as function(*args, **kwargs)
. In this way, args
will be the tuple of positional arguments and kwargs
will be the dictionary of keyword arguments. An example of such a decorator will be:
def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner
Chaining Decorators in Python
Multiple decorators can be chained in Python.
This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.
def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")
Output
****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************
The above syntax of,
@star @percent def printer(msg): print(msg)
is equivalent to
def printer(msg): print(msg) printer = star(percent(printer))
The order in which we chain decorators matter. If we had reversed the order as,
@percent @star def printer(msg): print(msg)
The output would be:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%