Dans l'article intitulé Deep Dive Into Python Decorators, j'ai présenté le concept de décorateurs Python, présenté de nombreux décorateurs cool et expliqué comment les utiliser..
Dans ce tutoriel, je vais vous montrer comment écrire vos propres décorateurs. Comme vous le verrez, écrire vos propres décorateurs vous donne beaucoup de contrôle et vous permet de nombreuses fonctionnalités. Sans décorateurs, ces capacités nécessiteraient beaucoup de passe-partout répétitifs générateurs d'erreurs qui encombrent votre code ou des mécanismes complètement externes tels que la génération de code.
Une récapitulation rapide si vous ne connaissez rien aux décorateurs. Un décorateur est un appelable (fonction, méthode, classe ou objet avec un appel()) qui accepte un appelable en entrée et renvoie un appelable en sortie. En règle générale, l'appelable retourné fait quelque chose avant et / ou après avoir appelé l'entrée appelable. Vous appliquez le décorateur en utilisant le @
Commençons par un «Bonjour tout le monde! décorateur. Ce décorateur remplacera totalement tout objet décoratif décoré par une fonction imprimant simplement "Hello World!".
python def hello_world (f): def décoré (* arguments, ** kwargs): imprimez 'Bonjour tout le monde!' retour décoré
C'est tout. Voyons-le en action, puis expliquons les différentes pièces et leur fonctionnement. Supposons que nous ayons la fonction suivante qui accepte deux numéros et imprime leur produit:
python def multiply (x, y): affiche x * y
Si vous invoquez, vous obtenez ce que vous attendez:
multiplier (6, 7) 42
Disons le décorer avec notre Bonjour le monde décorateur en annotant la multiplier fonctionner avec @Bonjour le monde
.
python @hello_world def multiply (x, y): affiche x * y
Maintenant, quand vous appelez multiplier avec tous les arguments (y compris les types de données incorrects ou le nombre d'arguments erroné), le résultat est toujours 'Hello World!' imprimé.
"python multiplie (6, 7) Bonjour tout le monde!
multiplier () Bonjour tout le monde!
multiplie ('zzz') Bonjour tout le monde! "
D'ACCORD. Comment ça marche? La fonction de multiplication d’origine a été complètement remplacée par la fonction décorée imbriquée dans la Bonjour le monde décorateur. Si nous analysons la structure de la Bonjour le monde décorateur alors vous verrez qu'il accepte l'entrée appelable F (qui n’est pas utilisé dans ce décorateur simple), il définit une fonction imbriquée appelée décoré qui accepte toute combinaison d'arguments et d'arguments de mots clés (def décoré (* args, ** kwargs)
), et finalement il retourne le décoré une fonction.
Il n'y a pas de différence entre écrire une fonction et un décorateur de méthode. La définition du décorateur sera la même. Le callable d’entrée sera soit une fonction régulière, soit une méthode liée.
Vérifions cela. Voici un décorateur qui imprime simplement l'appelable d'entrée et le type avant de l'invoquer. C’est très typique pour un décorateur d’effectuer une action et de continuer en invoquant le callable original..
python def print_callable (f): def decoré (* arguments, ** kwargs): print f, type (f) retourne f (* args, ** kwargs) retourne décoré
Notez la dernière ligne qui appelle l'entrée appelable de manière générique et renvoie le résultat. Ce décorateur est non intrusif en ce sens que vous pouvez décorer n’importe quelle fonction ou méthode dans une application opérationnelle. L’application continuera à fonctionner car la fonction décorée invoque l’original et a juste un effet secondaire peu avant..
Voyons cela en action. Je décorerai à la fois notre fonction de multiplication et une méthode.
"python @print_callable def multiply (x, y): affiche x * y
classe A (objet): @print_callable def foo (auto): affiche 'foo () ici "
Lorsque nous appelons la fonction et la méthode, l'appelable est imprimé, puis ils effectuent leur tâche d'origine:
"multiplier les pythons (6, 7)
A (). Foo ()
Les décorateurs peuvent aussi prendre des arguments. Cette possibilité de configurer le fonctionnement d'un décorateur est très puissante et vous permet d'utiliser le même décorateur dans de nombreux contextes..
Supposons que votre code est trop rapide et que votre patron vous demande de le ralentir un peu, car vous faites en sorte que les autres membres de l'équipe aient l'air mauvais. Ecrivons un décorateur qui mesure la durée d'exécution d'une fonction et si elle s'exécute en moins d'un certain nombre de secondes t, il attendra que t secondes n'expire, puis revienne.
Ce qui est différent maintenant est que le décorateur lui-même prend un argument t qui détermine le temps d’exécution minimum, et différentes fonctions peuvent être décorées avec différentes durées d’exécution. En outre, vous remarquerez que lors de l'introduction d'arguments de décorateur, deux niveaux d'imbrication sont nécessaires:
"heure d'importation en python
def minimum_runtime (t): def décoré (f): def wrapper (args, ** kwargs): start = time.time () result = f (args, ** kwargs) runtime = time.time () - démarre si runtime < t: time.sleep(t - runtime) return result return wrapper return decorated"
Déballons-le. Le décorateur lui-même - la fonction minimum_runtime prend une dispute t, qui représente le temps d’exécution minimum du callable décoré. L'entrée appelable F a été «poussé» vers le niché décoré fonction, et les arguments appelables d’entrée ont été «poussés» vers une autre fonction imbriquée emballage.
La logique réelle se déroule à l'intérieur du emballage une fonction. L'heure de début est enregistrée, l'appelant d'origine F est invoqué avec ses arguments et le résultat est stocké. Ensuite, l'exécution est vérifiée, et si elle est inférieure au minimum t puis il dort pour le reste du temps puis revient.
Pour le tester, je vais créer quelques fonctions qui se multiplient et les décorer avec des délais différents..
"python @minimum_runtime (1) par slow_multiply (x, y): multiplie (x, y)
@minimum_runtime (3) def slower_multiply (x, y): multiplie (x, y) "
Maintenant, je vais appeler multiplier directement ainsi que les fonctions plus lentes et mesurer le temps.
"heure d'importation en python
funcs = [multiplier, slow_multiply, slower_multiply] pour f dans funcs: start = time.time () f (6, 7) print f, time.time () - start "
Voici la sortie:
42 plaine
Comme vous pouvez le constater, la multiplication initiale ne prend presque pas de temps et les versions plus lentes sont en effet retardées en fonction de la durée d’exécution minimum.
Un autre fait intéressant est que la fonction décorée exécutée est le wrapper, ce qui est logique si vous suivez la définition du décoré. Mais cela pourrait être un problème, surtout si nous avons affaire à des décorateurs de piles. La raison en est que de nombreux décorateurs inspectent également leurs entrées appelables et vérifient son nom, sa signature et ses arguments. Les sections suivantes examineront cette question et donneront des conseils sur les meilleures pratiques..
Vous pouvez également utiliser des objets en tant que décorateurs ou renvoyer des objets à vos décorateurs. La seule exigence est qu’ils aient un __appel__() méthode, ils sont donc appelables. Voici un exemple de décorateur basé sur les objets qui compte le nombre de fois où sa fonction cible est appelée:
Classe python Compteur (objet): def __init __ (self, f): self.f = f self.called = 0 def __call __ (self, * arguments, ** kwargs): self.called + = 1 return self.f (* args, ** kwargs)
Ici c'est en action:
"python @Counter def bbb (): affiche 'bbb'
bbb () bbb
bbb () bbb
bbb () bbb
print bbb.called 3 "
C'est principalement une question de préférence personnelle. Les fonctions imbriquées et les fermetures de fonctions fournissent toute la gestion d'état proposée par les objets. Certaines personnes se sentent plus à l'aise avec des cours et des objets.
Dans la section suivante, je parlerai des décorateurs sages, et les décorateurs basés sur des objets prennent un peu de travail supplémentaire pour bien se comporter..
Les décorateurs à usage général peuvent souvent être empilés. Par exemple:
python @ decorator_1 @ decorator_2 def foo (): affiche 'foo () ici'
Lors de l'empilement de décorateurs, le décorateur extérieur (décorateur_1 dans ce cas) recevra le rappelable retourné par le décorateur intérieur (décorateur_2). Si decorator_1 dépend en quelque sorte du nom, des arguments ou de la chaîne de documentation de la fonction d'origine et que decorator_2 est implémenté naïvement, alors décorator_2 ne verra pas les informations correctes de la fonction d'origine, mais uniquement l'appelable renvoyé par decorator_2..
Par exemple, voici un décorateur qui vérifie que le nom de sa fonction cible est tout en minuscule:
python def check_lowercase (f): def decoré (* arguments, ** kwargs): assert f.func_name == f.func_name.lower () f (* arguments, ** kwargs) renvoie décoré
Décorez une fonction avec elle:
python @check_lowercase def Foo (): affiche 'Foo () ici'
L'appel de Foo () donne lieu à une assertion:
"plain In [51]: Foo () - Traceback AssertionErreur (l'appel le plus récent en dernier)