Décorateurs Python: comment l'utiliser et pourquoi?

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 firstet seconddonnent le même résultat. Ici, les noms firstet secondfont 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, filteret reduceen 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 Nonesi 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 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Articles intéressants...