Créer un framework de décorateur à usage général pour Python

Créons un "décorateur" Python. Créez un cadre de décorateur générique.

  1. Décorateur sans arguments
  2. Le décorateur passe des arguments
  3. Décorateur du cadre qui les intègre

Faire. Tout d'abord, nous présenterons le cadre du décorateur (objectif) et y travaillerons.

Cadre décorateur

Cet article est écrit pour être lu (vers le but en répétant des essais et des erreurs) et est une longue phrase, donc je vais d'abord décrire uniquement comment écrire le décorateur (cadre, cadre). [La version complète est répertoriée en dernier](# decorator_frameworkpy-full-version) y compris le test. Si vous êtes occupé, jetez-y un œil lorsque vous en aurez le temps.

decorator_framework.py


from functools import wraps

def my_decorator( *args, **kwargs ):
    #Pour les décorateurs qui ne prennent clairement aucun argument, partez d'ici
    # _my_Vous pouvez renommer le décorateur et le définir globalement.
    def _my_decorator( func ):
        # _my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
        print( "_my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici" )
        @wraps(func)
        def _my_decorator_body( *body_args, **body_kwargs ):
            #Le prétraitement se fait ici
            print( "Le prétraitement se fait ici", args, kwargs, body_args, body_kwargs )
            try:
                #Exécution du corps décoré
                ret = func( *body_args, **body_kwargs )
            except:
                raise
            #Le post-traitement se fait ici
            print( "Le post-traitement se fait ici", args, kwargs, body_args, body_kwargs )
            return ret

        #Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
        print( "Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2" )
        return _my_decorator_body

    #C'est la fin du décorateur qui ne prend clairement aucun argument

    #Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
    print( "Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1" )

    if len(args) == 1 and callable( args[0] ):
        #Si le décorateur est appelé sans argument, gérez-le ici
        print( "No arguments" )
        return _my_decorator( args[0] )

    else:
        #Si le décorateur est appelé avec un argument, traitez-le ici
        print( "There are some arguments:", args )
        return _my_decorator

Encore plus simple.

decorator_framework.py Version simple


from functools import wraps

def my_decorator( *args, **kwargs ):
    def _my_decorator( func ):
        # _my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
        @wraps(func)
        def _my_decorator_body( *body_args, **body_kwargs ):
            #Le prétraitement se fait ici
            try:
                #Exécution du corps décoré
                ret = func( *body_args, **body_kwargs )
            except:
                raise
            #Le post-traitement se fait ici
            return ret

        #Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
        return _my_decorator_body

    #Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1

    if len(args) == 1 and callable( args[0] ):
        #Si le décorateur est appelé sans argument, gérez-le ici
        return _my_decorator( args[0] )

    else:
        #Si le décorateur est appelé avec un argument, traitez-le ici
        return _my_decorator

Commençons maintenant par créer un décorateur.

Qu'est-ce qu'un décorateur?

Le glossaire décrit le décorateur comme suit:

decorator

(decorator) Une fonction qui renvoie une autre fonction, généralement @wrapper . Un cas d'utilisation courant pour les décorateurs est < span class = "pre"> classmethod () et staticmethod () .

La grammaire du décorateur est le sucre de syntaxe. Les deux définitions de fonction suivantes sont sémantiquement identiques:

def f(...):
    ...
f = staticmethod(f)

@staticmethod def f(...): ...

Le même concept existe dans les classes, mais il est rarement utilisé. Pour plus d'informations sur les décorateurs, consultez définition de fonction et définition de classe .

(Extrait de decorator-Glossary - Python 3.8.1 Documentation)

Dans l'exemple cité ci-dessus, «@ staticmethod» est le décorateur.

De plus, la description de l'élément de définition de fonction est la suivante.

Une ou plusieurs définitions de fonction decorator . Lorsque vous définissez une fonction, l'expression de décorateur est évaluée dans la portée qui contient la définition de fonction. Le résultat doit être un objet appelable qui prend un objet fonction comme seul argument. Au lieu d'un objet fonction, la valeur renvoyée est liée au nom de la fonction. Plusieurs décorateurs sont imbriqués et appliqués. Par exemple, le code suivant:

@f1(arg)
@f2
def func(): pass

équivaut à peu près à

def func(): pass
func = f1(arg)(f2(func))

Cependant, dans l'ancien code, il n'est pas possible de lier temporairement la fonction d'origine au nom func . Sauf là où il n'y en a pas.

(Cité de définition de fonction --8 instruction composée - documentation Python 3.8.1)

La grammaire du décorateur est le sucre de syntaxe

Certaines personnes peuvent ne pas être familières avec le mot «sucre syntaxique». En termes simples, une description complexe (obscurcie) qui peut être remplacée par une simple notation, ou vice versa, est appelée «sucre de syntaxe».

En d'autres termes, le décorateur est un remplaçant réinscriptible, similaire à ce que les autres langages appellent "macro". (Cependant, la méthode de remplacement a été décidée)

Prenons l'exemple de la citation du glossaire.

Exemple 1


def f(...):
    ...
f = staticmethod(f)  

Avec cet exemple 1

Exemple 2


@staticmethod
def f(...):
    ...

Il dit que cet exemple 2 est le même. Que voulez-vous dire.

Ce que fait l'exemple 1 est

  1. Définissez une fonction appelée «f ()».
  2. Exécutez staticmethod () avec la fonction définie f () (objet fonction) comme argument.
  3. Modifiez à nouveau la valeur de retour de staticmethod () à la fonction f. Cela signifie que.

L'exemple 2 en est une version simplifiée, et «@ staticmethod» est appelé un «décorateur».

Après cela, lorsque la fonction f () est réellement appelée, la fonction redéfinie est appelée comme valeur de retour de staticmethod ().

Structure décoratrice

Puisque @ staticmethod est en fait un décorateur standard, ce n'est pas pratique comme exemple, nous allons donc procéder comme @ my_decorator ci-dessous.

Il y a quelques choses que vous pouvez dire à partir de l'exemple ci-dessus seul.

  1. Le décorateur my_decorator est une fonction. (Pour être exact, "objet appelable")
  2. Decorator (function) my_decorator () prend un objet fonction comme argument.
  3. Decorator (function) my_decorator () renvoie une fonction.

à propos de ça.

Premier pas

Le code ci-dessous montre ce que nous avons trouvé ci-dessus.

my_decorator&nbsp;Premier pas


def my_decorator(func):
    return func

Il ne fait rien et renvoie la fonction reçue en argument. Les appels qui n'utilisent pas «@» ressembleront à l'exemple 001.

sample&nbsp;001


>>> def my_decorator_001(func):
...     return func
...
>>> def base_func():
...     print( "in base_func()" )
...
>>> base_func = my_decorator_001( base_func )
>>> base_func()
in base_func()
>>>

Si vous utilisez @ comme décorateur, il ressemblera à l'exemple 002.

sample&nbsp;002


>>> def my_decorator_001(func):
...     return func
...
>>> @my_decorator_001
... def base_func():
...     print( "in base_func()" )
...
>>> base_func()
in base_func()
>>>

Vous ne savez pas ce qui se passe avec ça. Cependant, il est à noter que lorsque vous entrez @ my_decorator_001, il attend une entrée continue.

Ajoutons un affichage dans my_decorator ().

sample&nbsp;003


>>> def my_decorator_002(func):
...     print( "in my_decorator_002()" )
...     return func
...
>>> def base_func():
...     print( "in base_func()" )
...
>>> base_func = my_decorator_002(base_func)
in my_decorator_002()
>>> base_func()
in base_func()
>>>

Ensuite, utilisons un décorateur.

sample&nbsp;004


>>> def my_decorator_002(func):
...     print( "in my_decorator_002()" )
...     return func
...
>>> @my_decorator_002
... def base_func():
...     print( "in base_func()" )
...
in my_decorator_002()
>>> base_func()
in base_func()
>>>

La fonction my_decorator_002 () n'est pas exécutée lorsque @ my_decorator_002 est écrit, et ʻin my_decorator ()est affiché lorsque la définition dedef base_func ():est complétée en dessous. Le point à noter est que c'est la même chose qui se produit lorsque vous exécutezbase_func = my_decorator_002 (base_func)` dans l'exemple 003.

Quelle est la fonction de retour?

Auparavant, la fonction donnée à l'argument du décorateur était retournée telle quelle avec return. Quelle est la fonction de retour?

Essayons d'abord d'utiliser une fonction globale pour voir ce qui se passe lorsque nous renvoyons une autre fonction.

sample&nbsp;005


>>> def global_func():
...     print( "in global_func()" )
...
>>> def my_decorator_005(func):
...     print( "in my_decorator_005()" )
...     return global_func
...
>>> @my_decorator_005
... def base_func():
...     print( "in base_func()" )
...
in my_decorator_005()
>>> base_func()
in global_func()
>>>

En exécutant base_func (), global_func () a été appelé sans aucun problème. Mais, bien sûr, l'original base_func () ne fonctionne pas.

Ajouter un traitement et exécuter la fonction d'origine

Pensez à exécuter la fonction d'origine lors de l'exécution de la nouvelle fonction.

La fonction d'origine est transmise en tant qu'argument de la fonction de décorateur, donc son appel exécutera la fonction d'origine.

sample&nbsp;006


>>> def global_func():
...     print( "in global_func()" )
...
>>> def my_decorator_006(func):
...     print( "in my_decorator_006()" )
...     func()  #Appel à la fonction d'origine
...     return global_func
...
>>> @my_decorator_006
... def base_func():
...     print( "in base_func()" )
...
in my_decorator_006()
in base_func()      #J'ai appelé la fonction d'origine ici
>>> base_func()     #* Je souhaite appeler la fonction d'origine ici
in global_func()
>>>

La fonction d'origine base_func () a été appelée lorsque @ my_decorator_006 a été spécifié. Le moment où vous voulez appeler base_func () est lorsque vous appelez base_func () avec "*". En d'autres termes, nous voulons appeler base_func () dans global_func ().

Modifions-le un peu pour qu'il puisse être appelé dans global_func ().

sample&nbsp;007


>>> #Conservez la fonction d'origine
... original_func = None
>>>
>>> def global_func():
...     global original_func
...     print( "in global_func()" )
...     original_func() #La fonction d'origine doit être appelée
...
>>> def my_decorator_007(func):
...     global original_func
...     print( "in my_decorator_007()" )
...     original_func = func #La fonction passée à l'argument est d'origine globale_Pour func
...     return global_func
...
>>> @my_decorator_007
... def base_func():
...     print( "in base_func()" )
...
in my_decorator_007()
>>> base_func()     #* Je souhaite appeler la fonction d'origine ici
in global_func()
in base_func()
>>>

C'est un peu déroutant, mais j'ai pu l'appeler à partir de global_func () en préparant la variable globale ʻoriginal_func et en sauvegardant le func` passé comme argument ici.

Cependant, cela pose un problème. Lorsque j'essaie d'utiliser le décorateur pour plusieurs fonctions, cela ne fonctionne pas comme prévu.

sample&nbsp;008


>>> #Conservez la fonction d'origine
... original_func = None
>>>
>>> def global_func():
...     global original_func
...     print( "in global_func()" )
...     original_func() #La fonction d'origine doit être appelée
...
>>> def my_decorator_007(func):
...     global original_func
...     print( "in my_decorator_007()" )
...     original_func = func #La fonction passée à l'argument est d'origine globale_Pour func
...     return global_func
...
>>> @my_decorator_007
... def base_func():
...     print( "in base_func()" )
...
in my_decorator_007()
>>> @my_decorator_007
... def base_func_2():
...     print( "in base_func_2()" )
...
in my_decorator_007()
>>> base_func()     # "in base_func()"Je veux que tu sois affiché
in global_func()
in base_func_2()    ← "in base_func()"ne pas"in base_func_2()"A été affiché
>>> base_func_2()   # "in base_func_2()"Je veux que tu sois affiché
in global_func()
in base_func_2()
>>>

L'appel à base_func () a exécuté base_func_2 (). Puisqu'il n'y a qu'une seule variable globale global_func, la dernière base_func_2 affectée est en cours d'exécution.

Permettre au décorateur d'être utilisé plusieurs fois (1)

Parlons de "Fermeture" pour que le décorateur puisse être utilisé plusieurs fois.

Jetez un œil à sample_009.py comme ceci:

sample_009.py


 1  def outer(outer_arg):
 2      def inner(inner_arg):
 3          # outer_arg et intérieur_Arg de sortie
 4          print( "outer_arg: " + outer_arg + ", inner_arg: " + inner_arg )
 5      return inner    # inner()Renvoie un objet fonction
 6
 7  f1 = outer("first")
 8  f2 = outer("second")
 9
10  f1("f1")
11  f2("f2")

Exécutez f2 = externe (" deuxième ") ʻ sur la ligne 8 après f1 = externe (" premier ") ʻ sur la ligne 7. Chacun se voit attribuer un objet fonction ʻinner, mais qu'en est-il de f1 (" f1 ")à la ligne 10 etf2 (" f2 ")` à la ligne 11.

Puisque la 8ème ligne dit f2 = external (" second "), à la 4ème ligne print (), ʻouter_arg devient "second" `.

La sortie de «f1 (« f1 »)» sur la ligne 10 et «f2 (« f2 »)» sur la ligne 11 sont respectivement.

outer_arg: second, inner_arg: f1
outer_arg: second, inner_arg: f2

Il semble que ...

Quand je l'exécute, cela ressemble à ceci:

sample&nbsp;009


>>> def outer(outer_arg):
...     def inner(inner_arg):
...         # outer_arg et intérieur_Arg de sortie
...         print( "outer_arg:", outer_arg, ", inner_arg:", inner_arg )
...     return inner    # inner()Renvoie un objet fonction
...
>>> f1 = outer("first")
>>> f2 = outer("second")
>>>
>>> f1("f1")
outer_arg: first, inner_arg: f1
>>> f2("f2")
outer_arg: second, inner_arg: f2
>>>

c'est,

  1. La définition de la fonction ʻinner () `` def inner (): ... est" exécutée "chaque fois que la fonction ʻouter () est appelée.
  2. Le ʻouter_argexterne référencé lorsque la fonction ʻinner ()est définie (exécutant la définition) est fixé (évalué) et maintenu par l'intérieur. C'est pour faire le mouvement. C'est ce qu'on appelle la fermeture. Les variables de la portée externe sont évaluées au moment de la définition, pas au moment de l'exécution.

Utilisez ceci pour permettre au décorateur d'être utilisé plusieurs fois.

Permettre au décorateur d'être utilisé plusieurs fois (2)

Dans l'exemple 009, nous avons utilisé une fonction globale, mais pour utiliser la fonction de fermeture, nous définissons la fonction à l'intérieur de la fonction décoratrice.

sample_010.py


def my_decorator_010(func):
    print( "in my_decorator_010()" )
    def inner():
        print( "in inner() and calling", func )
        func()
        print( "in inner() and returnd from ", func )
    print( "in my_decorator_010(), leaving ..." )
    return inner

@my_decorator_010
def base_func():
    print( "in base_func()" )

@my_decorator_010
def base_func_2():
    print( "in base_func_2()" )

base_func()     # "in base_func()"Je veux que tu sois affiché
base_func_2()   # "in base_func_2()"Je veux que tu sois affiché

Faisons ceci (entrez interactivement).

sample&nbsp;010


>>> def my_decorator_010(func):
...     print( "in my_decorator_010()" )
...     def inner():
...         print( "in inner() and calling", func )
...         func()
...         print( "in inner() and returnd from ", func )
...     print( "in my_decorator_010(), leaving ..." )
...     return inner
...
>>> @my_decorator_010
... def base_func():
...     print( "in base_func()" )
...
in my_decorator_010()
in my_decorator_010(), leaving ...
>>> @my_decorator_010
... def base_func_2():
...     print( "in base_func_2()" )
...
in my_decorator_010()
in my_decorator_010(), leaving ...
>>> base_func()     # "in base_func()"Je veux que tu sois affiché
in inner() and calling <function base_func at 0x769d1858>
in base_func()
in inner() and returnd from  <function base_func at 0x769d1858>
>>> base_func_2()   # "in base_func_2()"Je veux que tu sois affiché
in inner() and calling <function base_func_2 at 0x769d1930>
in base_func_2()
in inner() and returnd from  <function base_func_2 at 0x769d1930>
>>>

J'ai eu le résultat attendu.

Après avoir organisé ce qui précède, nous avons constaté que le décorateur devait être codé comme suit.

sample&nbsp;011


def my_decorator_011(func):
    def inner():
        #Écrivez le processus de pré-appel ici
        func()
        #Écrivez le traitement post-appel ici
    return inner

Décorateur pour les fonctions qui renvoient une valeur de retour

Les décorateurs jusqu'à présent sont

  1. Ne renvoyez pas de valeur
  2. Aucun argument J'ai pu l'utiliser pour la fonction.

Si vous souhaitez créer un décorateur à usage général, vous devez remplir les deux conditions ci-dessus.

Le programme prenant en compte la valeur de retour est le suivant.

sample_012.py


def my_decorator_012(func):
    def inner():
        #Écrivez le processus de pré-appel ici
        ret = func()
        #Écrivez le traitement post-appel ici
        return ret
    return inner

@my_decorator_012
def base_func_1():
    print( "in base_func_1()" )
    return 1

@my_decorator_012
def base_func_2():
    print( "in base_func_2()" )

r1 = base_func_1()
print( r1 )
base_func_2()

Le résultat de l'exécution est le suivant.

sample&nbsp;012


>>> def my_decorator_012(func):
...     def inner():
...         #Écrivez le processus de pré-appel ici
...         ret = func()
...         #Écrivez le traitement post-appel ici
...         return ret
...     return inner
...
>>> @my_decorator_012
... def base_func_1():
...     print( "in base_func_1()" )
...     return 1
...
>>> @my_decorator_012
... def base_func_2():
...     print( "in base_func_2()" )
...
>>> r1 = base_func_1()
in base_func_1()
>>> print( r1 )
1
>>> base_func_2()
in base_func_2()
>>>

Cela fonctionnait même s'il n'y avait pas de valeur de retour (base_func_2 ()).

Décorateur pour les fonctions qui prennent des arguments

Voir aussi: parameter-Glossary - Python 3.8.1 Documentation

Les arguments de fonction peuvent être reçus sous forme d'arguments formels pour lesquels le nombre d'arguments et la spécification de mot-clé ne sont pas déterminés par le paramètre de position de longueur variable et le paramètre de mot-clé de longueur variable.

  • Position de longueur variable : Étant donné n'importe quel nombre d'arguments de position (en plus de tous les arguments de position déjà reçus par d'autres arguments formels) Spécifier. Ces arguments formels doivent précéder le nom de l'argument formel, tel que args ci-dessous, * < Il peut être défini en ajoutant / code>:

    def func(*args, **kwargs): ...
    
  • Mots-clés de longueur variable : étant donné n'importe quel nombre d'arguments de mot-clé (en plus de tout argument de mot-clé déjà reçu par d'autres arguments formels) Est spécifié. Ces arguments formels doivent précéder le nom de l'argument formel, tel que kwargs dans l'exemple ci-dessus, ** Il peut être défini en ajoutant span> .

(Extrait du glossaire des paramètres - Documentation Python 3.8.1)

En termes simples, vous pouvez prendre un argument variable en spécifiant (* args, ** kwargs) comme argument de fonction.

En utilisant ceci, le décorateur d'une fonction qui prend un argument peut être écrit comme suit:

sample_013.py


def my_decorator_013(func):
    def inner( *args, **kwargs ):
        #Écrivez le processus de pré-appel ici
        ret = func( *args, **kwargs )
        #Écrivez le traitement post-appel ici
        return ret
    return inner

@my_decorator_013
def base_func_1(arg1, arg2, arg3="arg3"):
    print( "in base_func_1({}, {}, {})".format(arg1, arg2, arg3 ) )
    return 1

@my_decorator_013
def base_func_2():
    print( "in base_func_2()" )

r1 = base_func_1("arg1","arg2")
print( r1 )
base_func_2()

Voici le résultat de l'exécution.

sample&nbsp;013


>>> def my_decorator_013(func):
...     def inner( *args, **kwargs ):
...         #Écrivez le processus de pré-appel ici
...         ret = func( *args, **kwargs )
...         #Écrivez le traitement post-appel ici
...         return ret
...     return inner
...
>>> @my_decorator_013
... def base_func_1(arg1, arg2, arg3="arg3"):
...     print( "in base_func_1({}, {}, {})".format(arg1, arg2, arg3 ) )
...     return 1
...
>>> @my_decorator_013
... def base_func_2():
...     print( "in base_func_2()" )
...
>>> r1 = base_func_1("arg1","arg2")
in base_func_1(arg1, arg2, arg3)
>>> print( r1 )
1
>>> base_func_2()
in base_func_2()
>>>

Gestion des exceptions

Considérez également les exceptions.

sample_014.py


def my_decorator_014(func):
    def inner( *args, **kwargs ):
        #Écrivez le processus de pré-appel ici
        try:
            ret = func( *args, **kwargs )
        except:
            raise
        #Écrivez le traitement post-appel ici
        return ret
    return inner

@my_decorator_014
def base_func_1(arg1, arg2, arg3="arg3"):
    print( "in base_func_1({}, {}, {})".format(arg1, arg2, arg3 ) )
    return 1

@my_decorator_014
def base_func_2():
    print( "in base_func_2()" )
    na = 1 / 0      #Une exception de division zéro se produit

r1 = base_func_1("arg1","arg2")
print( r1 )

try:
    base_func_2()
except ZeroDivisionError:
    print( "Zero Division Error" )

Voici le résultat de l'exécution.

sample&nbsp;014


>>> def my_decorator_014(func):
...     def inner( *args, **kwargs ):
...         #Écrivez le processus de pré-appel ici
...         try:
...             ret = func( *args, **kwargs )
...         except:
...             raise
...         #Écrivez le traitement post-appel ici
...         return ret
...     return inner
...
>>> @my_decorator_014
... def base_func_1(arg1, arg2, arg3="arg3"):
...     print( "in base_func_1({}, {}, {})".format(arg1, arg2, arg3 ) )
...     return 1
...
>>> @my_decorator_014
... def base_func_2():
...     print( "in base_func_2()" )
...     na = 1 / 0      #Une exception de division zéro se produit
...
>>> r1 = base_func_1("arg1","arg2")
in base_func_1(arg1, arg2, arg3)
>>> print( r1 )
1
>>>
>>> try:
...     base_func_2()
... except ZeroDivisionError:
...     print( "Zero Division Error" )
...
in base_func_2()
Zero Division Error
>>>

Passez l'argument au décorateur

Puisqu'un décorateur est une fonction (un objet appelable), pensez à lui passer un argument.

La description de la définition de fonction citée au début comprenait également un exemple de décorateur avec des arguments.

@f1(arg)
@f2
def func(): pass
def func(): pass
func = f1(arg)(f2(func))

Puisque les décorateurs sont imbriqués, ne considérez qu'un seul décorateur pour plus de simplicité.

@my_decorator('argument')
def func(arg1, arg2, arg3):
    pass

Ceci est équivalent à ci-dessous.

def func(arg1, arg2, arg3):
    pass
func = my_decorator('argument')(func)

Cela signifie exécuter _my_decorator (func) en utilisant la fonction (supposée être _my_decorator) retournée en appelant my_decorator ('argument').

L'imbrication devient un pas plus profond, et la fonction la plus externe (décorateur) reçoit l'argument, et le décorateur existant y est inclus.

sample015.py


def my_decorator_015( arg1 ):
    def _my_decorator( func ):
        def inner( *args, **kwargs ):
            print( "in inner, arg1={}, func={}".format(arg1, func.__name__) )
            ret = func( *args, **kwargs )
            print( "in inner leaving ..." )
            return ret
        return inner
    return _my_decorator

Voici le résultat de l'exécution.

sample&nbsp;015


>>> def my_decorator_015( arg1 ):
...     def _my_decorator( func ):
...         def inner( *args, **kwargs ):
...             print( "in inner, arg1={}, func={}".format(arg1, func.__name__) )
...             ret = func( *args, **kwargs )
...             print( "in inner leaving ..." )
...             return ret
...         return inner
...     return _my_decorator
...
>>> @my_decorator_015('argument')
... def f_015( arg1, arg2, arg3 ):
...     print( "in func( {}, {}, {} )".format(arg1, arg2, arg3) )
...
>>> f_015( "Argument 1", "Argument 2", "Argument 3" )
in inner, arg1=argument, func=f_015
in func(Argument 1,Argument 2,Argument 3)
in inner leaving ...
>>>

Lorsque vous appelez le décorateur avec des arguments, la partie inférieure arg1 et func sont stockées dans _my_decorator_body.

             print( "in _my_decorator_body, arg1={}, func={}".format(arg1, func.__name__) )

Par conséquent, lorsque vous appelez f_015 (), les paramètres arg1 et func enregistrés sont référencés.

Une fois résumé

Décorateur sans arguments

Le décorateur qui ne prend aucun argument est le suivant.

sample_014.py


def my_decorator_014(func):
    def inner( *args, **kwargs ):
        #Écrivez le processus de pré-appel ici
        try:
            ret = func( *args, **kwargs )
        except:
            raise
        #Écrivez le traitement post-appel ici
        return ret
    return inner

Décorateur qui prend un argument

Le décorateur qui passe l'argument est le suivant.

sample_016.py


def my_decorator_016( arg1 ):
    def _my_decorator( func ):
        def inner( *args, **kwargs ):
            #Écrivez le processus de pré-appel ici
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            #Écrivez le traitement post-appel ici
            return ret
        return inner
    return _my_decorator

Décorateur qui absorbe la présence ou l'absence d'arguments

Que se passe-t-il si vous utilisez un décorateur qui assume des arguments sans donner d'arguments?

sample_017.py


def my_decorator_017( arg1 ):
    def _my_decorator( func ):
        def inner( *args, **kwargs ):
            #Écrivez le processus de pré-appel ici
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            #Écrivez le traitement post-appel ici
            return ret
        return inner
    return _my_decorator

@my_decorator_017
def f_017( arg1, arg2, arg3 ):
    print( "in {}( {}, {}, {} )".format(f_017.__name__, arg1, arg2, arg3) )

f_017( "Argument 1", "Argument 2", "Argument 3" )

Résultat d'exécution


$ python sample_017.py
Traceback (most recent call last):
  File "sample_017.py", line 21, in <module>
    f_017( "Argument 1", "Argument 2", "Argument 3" )
TypeError: _my_decorator() takes 1 positional argument but 3 were given
$

C'est parce que nous n'avons pas passé d'arguments à @ my_decorator_017, donc il s'appellef_017 = my_decorator_017 (f_017). Il y a deux façons d'éviter cela.

  1. Même si vous ne spécifiez pas d'argument pour le décorateur, assurez-vous d'ajouter () et de le recevoir comme argument de longueur variable.
  2. Si aucun argument n'est spécifié pour le décorateur, un seul objet fonction sera passé, il sera donc jugé.

Ceci est le premier exemple.

sample_018.py


def my_decorator_018( *args, **kwargs ):
    def _my_decorator( func ):
        def inner( *inner_args, **inner_kwargs ):
            #Écrivez le processus de pré-appel ici
            try:
                ret = func( *inner_args, **inner_kwargs )
            except:
                raise
            #Écrivez le traitement post-appel ici
            return ret
        return inner
    return _my_decorator

@my_decorator_018()
def f_018( arg1, arg2, arg3 ):
    print( "in {}( {}, {}, {} )".format(f_018.__name__, arg1, arg2, arg3) )

f_018( "Argument 1", "Argument 2", "Argument 3" )

Résultat d'exécution


$ python sample_018.py
in inner(Argument 1,Argument 2,Argument 3)

La deuxième méthode utilise le premier argument du décorateur pour le jugement.

sample_019.py


def my_decorator_019( *args, **kwargs ):
    def _my_decorator( func ):
        def inner( *inner_args, **inner_kwargs ):
            #Écrivez le processus de pré-appel ici
            try:
                ret = func( *inner_args, **inner_kwargs )
            except:
                raise
            #Écrivez le traitement post-appel ici
            return ret
        return inner
    if len(args) == 1 and callable(args[0]):
        return _my_decorator( args[0] )
    else:
        return _my_decorator

@my_decorator_019
def f_019_1( arg1, arg2, arg3 ):
    print( "in {}( {}, {}, {} )".format(f_019_1.__name__, arg1, arg2, arg3) )

@my_decorator_019()
def f_019_2( arg1, arg2, arg3 ):
    print( "in {}( {}, {}, {} )".format(f_019_2.__name__, arg1, arg2, arg3) )

@my_decorator_019('arg')
def f_019_3( arg1, arg2, arg3 ):
    print( "in {}( {}, {}, {} )".format(f_019_3.__name__, arg1, arg2, arg3) )

f_019_1( "Argument 1", "Argument 2", "Argument 3" )
f_019_2( "Argument 1", "Argument 2", "Argument 3" )
f_019_3( "Argument 1", "Argument 2", "Argument 3" )

Pour faire la distinction entre les arguments passés au décorateur et les arguments passés lorsque f_019_ * () est appelé, ils se distinguent respectivement par (* args, ** kwargs) et (* inner_args, ** inner_kwargs). Faire.

Résultat d'exécution


$ python sample_019.py
in inner(Argument 1,Argument 2,Argument 3)
in inner(Argument 1,Argument 2,Argument 3)
in inner(Argument 1,Argument 2,Argument 3)

Maintenant, j'aimerais penser qu'un cadre de décorateur à usage général a été créé ... Les noms des fonctions appelées (f_019_ * .__ name__) sont tous devenus ʻinner`.

Finale finale

Vous pouvez voir que la fonction ʻinner est appelée à la place de la fonction d'origine. Cependant, la fonction d'origine peut faire référence à des attributs tels que name (name) et documentation ( doc`).

Il existe un décorateur appelé «@ wraps» qui peut éviter cela.

Voir aussi: [@ functools.wraps () --functools --- Manipulation des fonctions d'ordre supérieur et des objets appelables - Documentation Python 3.8.1](https://docs.python.org/ja/3/library/functools.html? highlight = wraps # functools.wraps) Voir aussi: [functools.update_wrapper () --functools --- Manipulation des fonctions d'ordre supérieur et des objets appelables - Documentation Python 3.8.1](https://docs.python.org/ja/3/library/functools.html?highlight = wraps # functools.update_wrapper)

Si vous décorez ceci avant le def inner, vous pouvez envelopper et conserver les attributs de l'argument func transmis à la fonction externe (_my_decorator ()).

sample_020.py


from functools import wraps

def my_decorator_020( *args, **kwargs ):
    def _my_decorator( func ):
        @wraps(func)
        def inner( *inner_args, **inner_kwargs ):
            #Écrivez le processus de pré-appel ici
            try:
                ret = func( *inner_args, **inner_kwargs )
            except:
                raise
            #Écrivez le traitement post-appel ici
            return ret
        return inner
    if len(args) == 1 and callable(args[0]):
        return _my_decorator( args[0] )
    else:
        return _my_decorator

@my_decorator_020
def f_020_1( arg1, arg2, arg3 ):
    print( "in {}( {}, {}, {} )".format(f_020_1.__name__, arg1, arg2, arg3) )

@my_decorator_020()
def f_020_2( arg1, arg2, arg3 ):
    print( "in {}( {}, {}, {} )".format(f_020_2.__name__, arg1, arg2, arg3) )

@my_decorator_020('arg')
def f_020_3( arg1, arg2, arg3 ):
    print( "in {}( {}, {}, {} )".format(f_020_3.__name__, arg1, arg2, arg3) )

f_020_1( "Argument 1", "Argument 2", "Argument 3" )
f_020_2( "Argument 1", "Argument 2", "Argument 3" )
f_020_3( "Argument 1", "Argument 2", "Argument 3" )

Pour sample_019.py, @wraps (func) ʻest ajouté avant à partir de functools import wrapsetdef inner (). func est la fonction passée en argument à _my_decorator ()`.

Résultat d'exécution


$ python sample_020.py
in f_020_1(Argument 1,Argument 2,Argument 3)
in f_020_2(Argument 1,Argument 2,Argument 3)
in f_020_3(Argument 1,Argument 2,Argument 3)
$

Le nom de la fonction (f_019_ * .__ name__) est conservé comme nom de la fonction d'origine au lieu de ʻinner`.

Ça a été un long chemin, mais finalement le cadre du décorateur est terminé.

Depuis que je l'ai créé de manière générique, c'est def my_decorator (* args, ** kwargs):, mais si les arguments à recevoir sont fixes, il vaut mieux clarifier.

decorator_framework.py Full Version

decorator_framework.py&nbsp;Full&nbsp;Version


#!/usr/bin/env python
# -*- coding: utf-8 -*-

###########################################
#Décorateur(decorator)
###########################################
from functools import wraps

def my_decorator( *args, **kwargs ):
    """
    for doctest

    >>> @my_decorator
    ... def f1( arg1 ):
    ...     print( arg1 )
    ...
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
    No arguments
    _my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
    >>> @my_decorator('mytest1')
    ... def f2( arg2 ):
    ...     print( arg2 )
    ...
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
    There are some arguments: ('mytest1',)
    _my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
    >>> @my_decorator
    ... def f3( arg1 ):
    ...     print( arg1 )
    ...     a = 1/0
    ...
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
    No arguments
    _my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
    >>> @my_decorator('mytest2')
    ... def f4( arg2 ):
    ...     print( arg2 )
    ...     a = 1/0
    ...
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
    There are some arguments: ('mytest2',)
    _my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
    >>> try:
    ...     f1( "Hello, World! #1" )
    ... except:
    ...     print( "error #1" )
    ...
Le prétraitement se fait ici(<function f1 at 0x...>,) {} ('Hello, World! #1',) {}
    Hello, World! #1
Le post-traitement se fait ici... {} ('Hello, World! #1',) {}
    >>> try:
    ...         f2( "Hello, World! #2" )
    ... except:
    ...         print( "error #2" )
    ...
Le prétraitement se fait ici('mytest1',) {} ('Hello, World! #2',) {}
    Hello, World! #2
Le post-traitement se fait ici('mytest1',) {} ('Hello, World! #2',) {}
    >>> try:
    ...         f3( "Hello, World! #3" )
    ... except:
    ...         print( "error #3" )
    ...
Le prétraitement se fait ici(<function f3 at 0x...>,) {} ('Hello, World! #3',) {}
    Hello, World! #3
    error #3
    >>> try:
    ...         f4( "Hello, World! #4" )
    ... except:
    ...         print( "error #4" )
    ...
Le prétraitement se fait ici('mytest2',) {} ('Hello, World! #4',) {}
    Hello, World! #4
    error #4
    >>>
    """

    #Cliquez ici pour les décorateurs qui ne prennent clairement aucun argument
    # _my_Renommer le décorateur et définir globalement
    def _my_decorator( func ):
        # _my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
        print( "_my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici" )
        @wraps(func)
        def _my_decorator_body( *body_args, **body_kwargs ):
            #Le prétraitement se fait ici
            print( "Le prétraitement se fait ici", args, kwargs, body_args, body_kwargs )
            try:
                #Exécution du corps décoré
                ret = func( *body_args, **body_kwargs )
            except:
                raise
            #Le post-traitement se fait ici
            print( "Le post-traitement se fait ici", args, kwargs, body_args, body_kwargs )
            return ret

        #Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
        print( "Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2" )
        return _my_decorator_body

    #C'est la fin du décorateur qui ne prend clairement aucun argument

    #Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
    print( "Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1" )

    if len(args) == 1 and callable( args[0] ):
        #Si le décorateur est appelé sans argument, gérez-le ici
        print( "No arguments" )
        return _my_decorator( args[0] )

    else:
        #Si le décorateur est appelé avec un argument, traitez-le ici
        print( "There are some arguments:", args )
        return _my_decorator


###########################################
#   unitttest
###########################################
import unittest
from io import StringIO
import sys

class Test_My_Decorator(unittest.TestCase):
    def setUp(self):
        self.saved_stdout = sys.stdout
        self.stdout = StringIO()
        sys.stdout = self.stdout

    def tearDown(self):
        sys.stdout = self.saved_stdout

    def test_decorator_noarg(self):
        @my_decorator
        def t1(arg0):
            print( arg0 )

        t1("test_decorator_noarg")

        import re

        s = re.sub('0x[0-9a-f]+', '0x', self.stdout.getvalue())

        self.assertEqual(s,
            "Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1\n" +
            "No arguments\n" +
            "_my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici\n" +
            "Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2\n" +
            "Le prétraitement se fait ici(<function Test_My_Decorator.test_decorator_noarg.<locals>.t1 at 0x>,) {} ('test_decorator_noarg',) {}\n" +
            "test_decorator_noarg\n" +
            "Le post-traitement se fait ici(<function Test_My_Decorator.test_decorator_noarg.<locals>.t1 at 0x>,) {} ('test_decorator_noarg',) {}\n"
            )

    def test_decorator_witharg(self):
        @my_decorator('with arg')
        def t1(arg0):
            print( arg0 )

        t1("test_decorator_witharg")

        self.assertEqual(self.stdout.getvalue(),
            "Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1\n" +
            "There are some arguments: ('with arg',)\n" +
            "_my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici\n" +
            "Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2\n" +
            "Le prétraitement se fait ici('with arg',) {} ('test_decorator_witharg',) {}\n" +
            "test_decorator_witharg\n" +
            "Le post-traitement se fait ici('with arg',) {} ('test_decorator_witharg',) {}\n"
            )

    def test_functionname(self):
        @my_decorator
        def t1():
            return t1.__name__

        f_name = t1()

        self.assertEqual( f_name, "t1" )

    def test_docattribute(self):
        @my_decorator
        def t1():
            """Test Document"""
            pass

        self.assertEqual( t1.__doc__, "Test Document" )


###########################################
#   main
###########################################
if __name__ == '__main__':

    @my_decorator
    def f1( arg1 ):
        print( arg1 )

    @my_decorator('mytest1')
    def f2( arg2 ):
        print( arg2 )

    @my_decorator
    def f3( arg1 ):
        print( arg1 )
        a = 1/0

    @my_decorator('mytest2')
    def f4( arg2 ):
        print( arg2 )
        a = 1/0

    try:
        f1( "Hello, World! #1" )
    except:
        print( "error #1" )

    try:
        f2( "Hello, World! #2" )
    except:
        print( "error #2" )

    try:
        f3( "Hello, World! #3" )
    except:
        print( "error #3" )

    try:
        f4( "Hello, World! #4" )
    except:
        print( "error #4" )

    import doctest
    doctest.testmod(optionflags=doctest.ELLIPSIS)

    unittest.main()

Exemple d'exécution


$ python decorator_framework.py
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
No arguments
_my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
There are some arguments: ('mytest1',)
_my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
No arguments
_my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#1
There are some arguments: ('mytest2',)
_my_decorator_body()S'il y a un traitement nécessaire avant de définir, écrivez-le ici
Si un traitement est nécessaire lorsque le décorateur est décrit, écrivez-le ici#2
Le prétraitement se fait ici(<function f1 at 0x76973a08>,) {} ('Hello, World! #1',) {}
Hello, World! #1
Le post-traitement se fait ici(<function f1 at 0x76973a08>,) {} ('Hello, World! #1',) {}
Le prétraitement se fait ici('mytest1',) {} ('Hello, World! #2',) {}
Hello, World! #2
Le post-traitement se fait ici('mytest1',) {} ('Hello, World! #2',) {}
Le prétraitement se fait ici(<function f3 at 0x7685bd20>,) {} ('Hello, World! #3',) {}
Hello, World! #3
error #3
Le prétraitement se fait ici('mytest2',) {} ('Hello, World! #4',) {}
Hello, World! #4
error #4
....
----------------------------------------------------------------------
Ran 4 tests in 0.005s

OK
$

unittest


$ python -m unittest -v decorator_framework.py
test_decorator_noarg (decorator_framework.Test_My_Decorator) ... ok
test_decorator_witharg (decorator_framework.Test_My_Decorator) ... ok
test_docattribute (decorator_framework.Test_My_Decorator) ... ok
test_functionname (decorator_framework.Test_My_Decorator) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.006s

OK
$

Recommended Posts