Python est un langage adapté aux débutants. Cependant, il possède également de nombreuses fonctionnalités avancées et difficiles à maîtriser, comme les décorateurs. De nombreux débutants n'ont jamais compris les décorateurs et leur fonctionnement. Dans cet article, nous présenterons les tenants et les aboutissants des décorateurs.
En Python, une fonction est une structure très flexible. On peut l'attribuer à une variable, la passer en paramètre à une autre fonction, ou l'utiliser comme sortie d'une fonction. Un décorateur est essentiellement une fonction qui permet à d’autres fonctions d’ajouter des fonctionnalités sans modification.
C'est le sens de "décoration". Cette "décoration" représente elle-même une fonction. Si vous l'utilisez pour modifier différentes fonctions, cela signifie ajouter cette fonction à ces fonctions.
De manière générale, on peut utiliser le @sucre syntaxique (Syntactic Sugar) fourni par le décorateur pour décorer d'autres fonctions ou objets. Comme indiqué ci-dessous, nous utilisons le décorateur @dec pour décorer la fonction func () :
@dec def func(): pass
La meilleure façon de comprendre le décorateur est de comprendre quel problème le décorateur résout. Cet article présentera le décorateur étape par étape en partant du spécifique. problème et montrer son élégance et sa puissance.
Pour comprendre le but des décorateurs, regardons un exemple simple. Supposons que vous ayez une simple fonction d'addition dec.py avec une valeur par défaut de 10 pour le deuxième paramètre :
# dec.py def add(x, y=10): return x + y
Examinons de plus près cette fonction d'addition :
>>> add(10, 20) 30 >>> add>>> add.__name__ 'add' >>> add.__module__ '__main__' >>> add.__defaults__ # default value of the `add` function (10,) >>> add.__code__.co_varnames # the variable names of the `add` function ('x', 'y')
Nous n'avons pas besoin de comprendre de quoi il s'agit, rappelez-vous simplement Chaque fonction en Python est un objet et possède diverses propriétés et méthodes. Vous pouvez également afficher le code source de la fonction add() via le module inspect :
>>> from inspect import getsource >>> print(getsource(add)) def add(x, y=10): return x + y
Maintenant, vous utilisez la fonction d'addition d'une manière ou d'une autre, par exemple, vous utilisez certaines opérations pour tester la fonction :
# dec.py from time import time def add(x, y=10): return x + y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) Output: i add(10) 20 add(20, 30) 50 add("a", "b") ab
Si vous souhaitez comprendre la fonction de chaque opération Pour le temps, vous pouvez appeler le module time :
# dec.py from time import time def add(x, y=10): return x + y before = time() print('add(10)', add(10)) after = time() print('time taken: ', after - before) before = time() print('add(20, 30)', add(20, 30)) after = time() print('time taken: ', after - before) before = time() print('add("a", "b")', add("a", "b")) after = time() print('time taken: ', after - before) Output: add(10) 20 time taken:6.699562072753906e-05 add(20, 30) 50 time taken:6.9141387939453125e-06 add("a", "b") ab time taken:6.9141387939453125e-06
Maintenant, en tant que programmeur, cela vous démange un peu Après tout, nous n'aimons pas copier et coller le même code tout le temps. Le code actuel n'est pas très lisible. Si vous souhaitez changer quelque chose, vous devez tout modifier là où il apparaît. Il doit y avoir une meilleure méthode en Python.
Nous pouvons capturer le temps d'exécution directement dans la fonction add comme suit :
# dec.py from time import time def add(x, y=10): before = time() rv = x + y after = time() print('time taken: ', after - before) return rv print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b"))
Cette méthode est nettement meilleure que la précédente. Mais si vous avez une autre fonction, cela ne semble pas pratique. Lorsque nous avons plusieurs fonctions :
# dec.py from time import time def add(x, y=10): before = time() rv = x + y after = time() print('time taken: ', after - before) return rv def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
Parce que add et sub sont tous deux des fonctions, nous pouvons en profiter pour écrire une fonction timer. Nous voulons que la minuterie calcule le temps de fonctionnement d'une fonction :
def timer(func, x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv
C'est bien, mais nous devons envelopper différentes fonctions avec des fonctions de minuterie, comme ceci :
print('add(10)', timer(add,10)))
La valeur par défaut est-elle toujours 10 maintenant ? pas nécessairement. Alors, comment faire mieux ?
Voici une idée : créez une nouvelle fonction de minuterie, encapsulez d'autres fonctions et renvoyez la fonction encapsulée :
def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f
Maintenant, vous encapsulez simplement les fonctions d'ajout et de sous-fonction avec timer :
add = timer(add)
C'est tout ! Voici le code complet :
# dec.py from time import time def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f def add(x, y=10): return x + y add = timer(add) def sub(x, y=10): return x - y sub = timer(sub) print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30)) Output: time taken:0.0 add(10) 20 time taken:9.5367431640625e-07 add(20, 30) 50 time taken:0.0 add("a", "b") ab time taken:9.5367431640625e-07 sub(10) 0 time taken:9.5367431640625e-07 sub(20, 30) -10
Résumons le processus : nous avons une fonction (comme la fonction add) puis enveloppons cette fonction avec une action (comme le timing). Le résultat du packaging est une nouvelle fonction qui peut implémenter certaines nouvelles fonctions.
Bien sûr, il y a un problème avec les valeurs par défaut, nous le corrigerons plus tard.
Maintenant, la solution ci-dessus est très proche de l'idée d'un décorateur. Elle utilise des comportements courants pour envelopper une fonction spécifique. Le code après avoir utilisé le décorateur est :
def add(x, y=10): return x + y add = timer(add) You write: @timer def add(x, y=10): return x + y
Ils ont le même effet, c'est ce que font les décorateurs Python. La fonction qu'il implémente est similaire à add = timer(add), sauf que le décorateur met la syntaxe au-dessus de la fonction, et la syntaxe est plus simple : @timer.
# dec.py from time import time def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f @timer def add(x, y=10): return x + y @timer def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
Maintenant, il reste encore un petit problème qui n'a pas été résolu. Dans la fonction timer, nous codons en dur les paramètres x et y, c'est-à-dire que la valeur par défaut de y est 10. Il existe un moyen de transmettre des arguments et des arguments de mots-clés à la fonction, à savoir *args et **kwargs. Les paramètres sont les paramètres standard de la fonction (dans ce cas, x est le paramètre) et les paramètres mot-clé sont des paramètres qui ont déjà une valeur par défaut (dans ce cas, y=10). Le code est le suivant :
# dec.py from time import time def timer(func): def f(*args, **kwargs): before = time() rv = func(*args, **kwargs) after = time() print('time taken: ', after - before) return rv return f @timer def add(x, y=10): return x + y @timer def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
Maintenant, la fonction timer peut gérer n'importe quelle fonction, n'importe quel paramètre et n'importe quel réglage de valeur par défaut, car elle transmet simplement ces paramètres dans la fonction.
Vous vous demandez peut-être : si nous pouvons envelopper une fonction avec une autre fonction pour ajouter un comportement utile, pouvons-nous aller plus loin ? Devons-nous envelopper une fonction avec une autre fonction et être enveloppés par une autre fonction ?
Oui ! En fait, la fonction peut être aussi profonde que vous le souhaitez. Par exemple, vous souhaitez écrire un décorateur qui exécute une fonction n fois. Comme indiqué ci-dessous :
def ntimes(n): def inner(f): def wrapper(*args, **kwargs): for _ in range(n): rv = f(*args, **kwargs) return rv return wrapper return inner
Ensuite, vous pouvez utiliser la fonction ci-dessus pour envelopper une autre fonction, telle que la fonction add de l'article précédent :
@ntimes(3) def add(x, y): print(x + y) return x + y
L'instruction de sortie montre que le code est effectivement exécuté 3 fois.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!