Erstellen Sie ein universelles Dekorationsframework für Python

Lassen Sie uns einen Python "Dekorator" erstellen. Erstellen Sie ein generisches Dekorations-Framework.

  1. Dekorateur ohne Argumente
  2. Dekorateur übergibt Argumente
  3. Dekorateur des Frameworks, das diese integriert

Zu machen. Zuerst werden wir das Dekorations-Framework (Ziel) vorstellen und darauf hinarbeiten.

Dekorateur Rahmen

Dieser Artikel wurde geschrieben, um vorgelesen zu werden (in Richtung des Ziels durch Wiederholen von Versuch und Irrtum) und ist ein langer Satz, daher werde ich nur beschreiben, wie der Dekorateur (Framework, Framework) zuerst geschrieben wird. [Vollversion ist zuletzt aufgeführt](# decorator_frameworkpy-full-version) einschließlich des Tests. Wenn Sie beschäftigt sind, schauen Sie es sich bitte an, wenn Sie Zeit haben.

decorator_framework.py


from functools import wraps

def my_decorator( *args, **kwargs ):
    #Für Dekorateure, die eindeutig keine Argumente vertreten, beginnen Sie hier
    # _my_Es ist auch möglich, den Namen des Dekorateurs zu ändern und global zu definieren
    def _my_decorator( func ):
        # _my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
        print( "_my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier" )
        @wraps(func)
        def _my_decorator_body( *body_args, **body_kwargs ):
            #Die Vorverarbeitung erfolgt hier
            print( "Die Vorverarbeitung erfolgt hier", args, kwargs, body_args, body_kwargs )
            try:
                #Ausführung des dekorierten Körpers
                ret = func( *body_args, **body_kwargs )
            except:
                raise
            #Die Nachbearbeitung erfolgt hier
            print( "Die Nachbearbeitung erfolgt hier", args, kwargs, body_args, body_kwargs )
            return ret

        #Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
        print( "Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2" )
        return _my_decorator_body

    #Dies ist das Ende des Dekorateurs, das eindeutig kein Argument annimmt

    #Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
    print( "Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1" )

    if len(args) == 1 and callable( args[0] ):
        #Wenn der Dekorateur ohne Argumente aufgerufen wird, behandeln Sie dies hier
        print( "No arguments" )
        return _my_decorator( args[0] )

    else:
        #Wenn der Dekorateur mit einem Argument aufgerufen wird, verarbeiten Sie es hier
        print( "There are some arguments:", args )
        return _my_decorator

Noch einfacher.

decorator_framework.py Einfache Version


from functools import wraps

def my_decorator( *args, **kwargs ):
    def _my_decorator( func ):
        # _my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
        @wraps(func)
        def _my_decorator_body( *body_args, **body_kwargs ):
            #Die Vorverarbeitung erfolgt hier
            try:
                #Ausführung des dekorierten Körpers
                ret = func( *body_args, **body_kwargs )
            except:
                raise
            #Die Nachbearbeitung erfolgt hier
            return ret

        #Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
        return _my_decorator_body

    #Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1

    if len(args) == 1 and callable( args[0] ):
        #Wenn der Dekorateur ohne Argumente aufgerufen wird, behandeln Sie dies hier
        return _my_decorator( args[0] )

    else:
        #Wenn der Dekorateur mit einem Argument aufgerufen wird, verarbeiten Sie es hier
        return _my_decorator

Beginnen wir nun mit der Erstellung eines Dekorateurs.

Was ist ein Dekorateur?

Das Glossar beschreibt den Dekorateur wie folgt:

decorator

(decorator) Eine Funktion, die eine andere Funktion zurückgibt, normalerweise @wrapper angewendet. Ein häufiger Anwendungsfall für Dekorateure ist < span class = "pre"> classmethod () und staticmethod () .

Die Grammatik des Dekorateurs ist Syntaxzucker. Die folgenden zwei Funktionsdefinitionen sind semantisch identisch:

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

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

Das gleiche Konzept existiert in Klassen, wird jedoch selten verwendet. Weitere Informationen zu Dekorateuren finden Sie unter Funktionsdefinition und Klassendefinition .

(Zitiert aus Dekorator-Glossar - Python 3.8.1-Dokumentation)

In dem oben zitierten Beispiel ist "@ staticmethod" der Dekorateur.

Darüber hinaus lautet die Beschreibung des Funktionsdefinitionselements wie folgt.

Eine oder mehrere Funktionsdefinitionen decorator umschließen. Wenn Sie eine Funktion definieren, wird der Dekoratorausdruck in dem Bereich ausgewertet, der die Funktionsdefinition enthält. Das Ergebnis muss ein aufrufbares Objekt sein, dessen einziges Argument ein Funktionsobjekt ist. Anstelle eines Funktionsobjekts ist der zurückgegebene Wert an den Funktionsnamen gebunden. Mehrere Dekorateure werden verschachtelt und angewendet. Zum Beispiel der folgende Code:

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

entspricht in etwa

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

Im vorherigen Code ist es jedoch nicht möglich, die ursprüngliche Funktion vorübergehend an den Namen func zu binden. Außer wo es so etwas nicht gibt.

(Zitiert aus Funktionsdefinition - 8 zusammengesetzte Anweisung - Python 3.8.1-Dokumentation)

Die Grammatik des Dekorateurs ist Syntaxzucker

Einige Leute kennen das Wort "Synthaxzucker" möglicherweise nicht. Einfach ausgedrückt, eine komplexe (verschleierte) Beschreibung, die durch eine einfache Notation ersetzt werden kann oder umgekehrt, wird als "Syntaxzucker" bezeichnet.

Mit anderen Worten, der Dekorator ist ein wiederbeschreibbarer Ersatz, ähnlich dem, was andere Sprachen als "Makro" bezeichnen. (Die Ersetzungsmethode wurde jedoch festgelegt.)

Betrachten Sie das Beispiel im Glossar.

Beispiel 1


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

Mit diesem Beispiel 1

Beispiel 2


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

Es heißt, dass dieses Beispiel 2 dasselbe ist. Was meinst du.

Was Beispiel 1 tut, ist

  1. Definieren Sie eine Funktion namens "f ()".
  2. Führen Sie staticmethod () mit der definierten Funktion f () (Funktionsobjekt) als Argument aus.
  3. Ändern Sie den Rückgabewert von staticmethod () erneut in die Funktion f. Es bedeutet das.

Beispiel 2 ist eine vereinfachte Version davon, und "@ staticmethod" wird als "Dekorateur" bezeichnet.

Wenn danach die Funktion "f ()" tatsächlich aufgerufen wird, wird die neu definierte Funktion als Rückgabewert von "staticmethod ()" aufgerufen.

Dekorateurstruktur

Da @ staticmethod tatsächlich ein standarddefinierter Dekorator ist, ist es als Beispiel nicht praktisch, daher werden wir unten als @ my_decorator fortfahren.

Allein anhand des obigen Beispiels können Sie einige Dinge erkennen.

  1. Der Dekorator my_decorator ist eine Funktion. (Um genau zu sein, "aufrufbares Objekt")
  2. Decorator (Funktion) my_decorator () nimmt ein Funktionsobjekt als Argument.
  3. Decorator (Funktion) my_decorator () gibt eine Funktion zurück.

darüber.

Erster Schritt

Der folgende Code zeigt, was wir oben gefunden haben.

my_decorator&nbsp;Erster Schritt


def my_decorator(func):
    return func

Es tut nichts und gibt die als Argument empfangene Funktion zurück. Aufrufe, die kein @ verwenden, sehen aus wie Beispiel 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()
>>>

Wenn Sie "@" als Dekorateur verwenden, sieht es wie Beispiel 002 aus.

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()
>>>

Sie wissen nicht, was damit los ist. Es ist jedoch bemerkenswert, dass bei der Eingabe von "@ my_decorator_001" auf eine kontinuierliche Eingabe gewartet wird.

Fügen wir eine Anzeige in "my_decorator ()" hinzu.

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()
>>>

Als nächstes verwenden wir einen Dekorateur.

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()
>>>

Die Funktion "my_decorator_002 ()" wird nicht ausgeführt, wenn "@ my_decorator_002" geschrieben wird, und "in my_decorator ()" wird angezeigt, wenn die Definition von "def base_func ():" darunter abgeschlossen ist. Der zu beachtende Punkt ist, dass dies dasselbe ist, was passiert, wenn Sie in Beispiel 003 base_func = my_decorator_002 (base_func) ausführen.

Was ist die Rückgabefunktion?

Zuvor wurde die Funktion, die dem Argument des Dekorateurs zugewiesen wurde, wie bei return zurückgegeben. Was ist die Rückgabefunktion?

Versuchen wir zunächst, eine globale Funktion zu verwenden, um zu sehen, was passiert, wenn wir eine andere Funktion zurückgeben.

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()
>>>

Durch Ausführen von "base_func ()" wurde "global_func ()" ohne Probleme aufgerufen. Aber natürlich läuft das ursprüngliche base_func () nicht.

Fügen Sie die Verarbeitung hinzu und führen Sie die ursprüngliche Funktion aus

Erwägen Sie, die ursprüngliche Funktion auszuführen, während Sie die neue Funktion ausführen.

Die ursprüngliche Funktion wird als Argument der Decorator-Funktion übergeben. Wenn Sie sie also aufrufen, wird die ursprüngliche Funktion ausgeführt.

sample&nbsp;006


>>> def global_func():
...     print( "in global_func()" )
...
>>> def my_decorator_006(func):
...     print( "in my_decorator_006()" )
...     func()  #Rufen Sie die ursprüngliche Funktion auf
...     return global_func
...
>>> @my_decorator_006
... def base_func():
...     print( "in base_func()" )
...
in my_decorator_006()
in base_func()      #Ich habe hier die ursprüngliche Funktion aufgerufen
>>> base_func()     #* Ich möchte hier die ursprüngliche Funktion aufrufen
in global_func()
>>>

Die ursprüngliche Funktion "base_func ()" wurde aufgerufen, als "@ my_decorator_006" angegeben wurde. Das Timing, wenn Sie "base_func ()" aufrufen möchten, ist, wenn Sie "base_func ()" mit "*" aufrufen. Mit anderen Worten, wir wollen "base_func ()" innerhalb von "global_func ()" aufrufen.

Lassen Sie es uns ein wenig ändern, damit es in global_func () aufgerufen werden kann.

sample&nbsp;007


>>> #Behalten Sie die ursprüngliche Funktion bei
... original_func = None
>>>
>>> def global_func():
...     global original_func
...     print( "in global_func()" )
...     original_func() #Die ursprüngliche Funktion sollte aufgerufen werden
...
>>> def my_decorator_007(func):
...     global original_func
...     print( "in my_decorator_007()" )
...     original_func = func #Die an das Argument übergebene Funktion ist ein globales Original_Zu func
...     return global_func
...
>>> @my_decorator_007
... def base_func():
...     print( "in base_func()" )
...
in my_decorator_007()
>>> base_func()     #* Ich möchte hier die ursprüngliche Funktion aufrufen
in global_func()
in base_func()
>>>

Es ist ein wenig verwirrend, aber ich konnte es von "global_func ()" aus aufrufen, indem ich die globale Variable "original_func" vorbereitete und die hier als Argument übergebene "func" speicherte.

Es gibt jedoch ein Problem damit. Wenn ich versuche, den Dekorator für mehrere Funktionen zu verwenden, funktioniert er nicht wie erwartet.

sample&nbsp;008


>>> #Behalten Sie die ursprüngliche Funktion bei
... original_func = None
>>>
>>> def global_func():
...     global original_func
...     print( "in global_func()" )
...     original_func() #Die ursprüngliche Funktion sollte aufgerufen werden
...
>>> def my_decorator_007(func):
...     global original_func
...     print( "in my_decorator_007()" )
...     original_func = func #Die an das Argument übergebene Funktion ist ein globales Original_Zu 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()"Ich möchte, dass du angezeigt wirst
in global_func()
in base_func_2()    ← "in base_func()"nicht"in base_func_2()"Wurde angezeigt
>>> base_func_2()   # "in base_func_2()"Ich möchte, dass du angezeigt wirst
in global_func()
in base_func_2()
>>>

Der Aufruf von "base_func ()" hat "base_func_2 ()" ausgeführt. Da es nur eine globale Variable "global_func" gibt, wird die zuletzt zugewiesene "base_func_2" ausgeführt.

Lassen Sie den Dekorateur mehrmals verwenden (1)

Lassen Sie uns über "Schließen" sprechen, damit der Dekorateur mehrmals verwendet werden kann.

Schauen Sie sich sample_009.py so an:

sample_009.py


 1  def outer(outer_arg):
 2      def inner(inner_arg):
 3          # outer_arg und inner_Ausgabe arg
 4          print( "outer_arg: " + outer_arg + ", inner_arg: " + inner_arg )
 5      return inner    # inner()Gibt ein Funktionsobjekt zurück
 6
 7  f1 = outer("first")
 8  f2 = outer("second")
 9
10  f1("f1")
11  f2("f2")

Führen Sie nach "f1 = äußere (" erste ")" in Zeile 7 "f2 = äußere (" zweite ")" in Zeile 8 aus. Jedem wird ein "inneres" Funktionsobjekt zugewiesen, aber was ist mit "f1 (" f1 ")" in Zeile 10 und "f2 (" f2 ")" in Zeile 11?

In der 8. Zeile steht "f2 = Outer (" second ")", in der 4. Zeile "print ()" wird "Outer_arg" zu "Second".

Die Ausgabe von "f1 (" f1 ")" in Zeile 10 und "f2 (" f2 ")" in Zeile 11 sind jeweils.

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

Es scheint so als ...

Wenn ich es starte, sieht es so aus:

sample&nbsp;009


>>> def outer(outer_arg):
...     def inner(inner_arg):
...         # outer_arg und inner_Ausgabe arg
...         print( "outer_arg:", outer_arg, ", inner_arg:", inner_arg )
...     return inner    # inner()Gibt ein Funktionsobjekt zurück
...
>>> f1 = outer("first")
>>> f2 = outer("second")
>>>
>>> f1("f1")
outer_arg: first, inner_arg: f1
>>> f2("f2")
outer_arg: second, inner_arg: f2
>>>

das ist,

  1. Die Funktionsdefinition def inner (): ... inner () wird jedes Mal "ausgeführt", wenn die Funktionäußere ()aufgerufen wird.
  2. Das äußere "äußere_arg", auf das verwiesen wird, wenn die "innere ()" Funktion definiert ist (die Definition ausführt), wird vom inneren bestimmt (ausgewertet) und gehalten. Dies ist die Bewegung zu machen. Dies wird als Schließung bezeichnet. Variablen im äußeren Bereich werden zur Definitionszeit und nicht zur Laufzeit ausgewertet.

Verwenden Sie diese Option, damit der Dekorateur mehrmals verwendet werden kann.

Lassen Sie den Dekorateur mehrmals verwenden (2)

In Beispiel 009 haben wir eine globale Funktion verwendet, aber um die Funktion des Verschlusses zu verwenden, definieren wir die Funktion innerhalb der Dekorationsfunktion.

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()"Ich möchte, dass du angezeigt wirst
base_func_2()   # "in base_func_2()"Ich möchte, dass du angezeigt wirst

Lassen Sie uns dies tun (interaktiv eingeben).

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()"Ich möchte, dass du angezeigt wirst
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()"Ich möchte, dass du angezeigt wirst
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>
>>>

Ich habe das erwartete Ergebnis erhalten.

Nachdem wir das Obige organisiert hatten, stellten wir fest, dass der Dekorateur wie folgt codiert werden sollte.

sample&nbsp;011


def my_decorator_011(func):
    def inner():
        #Schreiben Sie hier den Pre-Call-Prozess
        func()
        #Schreiben Sie hier die Nachbearbeitung
    return inner

Decorator für Funktionen, die einen Rückgabewert zurückgeben

Die Dekorateure sind bisher

  1. Geben Sie keinen Wert zurück
  2. Keine Argumente Ich konnte es für die Funktion verwenden.

Wenn Sie einen Allzweckdekorateur herstellen möchten, müssen Sie die beiden oben genannten Bedingungen erfüllen.

Das Programm, das den Rückgabewert berücksichtigt, ist wie folgt.

sample_012.py


def my_decorator_012(func):
    def inner():
        #Schreiben Sie hier den Pre-Call-Prozess
        ret = func()
        #Schreiben Sie hier die Nachbearbeitung
        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()

Das Ausführungsergebnis ist wie folgt.

sample&nbsp;012


>>> def my_decorator_012(func):
...     def inner():
...         #Schreiben Sie hier den Pre-Call-Prozess
...         ret = func()
...         #Schreiben Sie hier die Nachbearbeitung
...         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()
>>>

Es funktionierte auch, wenn es keinen Rückgabewert gab (base_func_2 ()).

Dekorateur für Funktionen, die Argumente annehmen

Siehe auch: Parameter-Glossar - Python 3.8.1-Dokumentation

Funktionsargumente können als formale Argumente empfangen werden, für die die Anzahl der Argumente und die Schlüsselwortspezifikation nicht durch den Positionsparameter variabler Länge und den Schlüsselwortparameter variabler Länge bestimmt werden.

  • Position variabler Länge : Beliebig viele Positionsargumente (zusätzlich zu Positionsargumenten, die bereits von anderen formalen Argumenten empfangen wurden) Angegeben. Solche formalen Argumente sollten vor dem formalen Argumentnamen stehen, z. B. args unten, * < Sie kann durch Hinzufügen von / code>: definiert werden

    def func(*args, **kwargs): ...
    
  • Schlüsselwörter mit variabler Länge : Bei einer beliebigen Anzahl von Schlüsselwortargumenten (zusätzlich zu Schlüsselwortargumenten, die bereits von anderen formalen Argumenten empfangen wurden) Angegeben. Solche formalen Argumente sollten vor dem formalen Argumentnamen stehen, z. B. kwargs im obigen Beispiel, ** Sie kann durch Hinzufügen von span> definiert werden.

(Zitiert aus Parameter-Glossar - Python 3.8.1-Dokumentation)

Einfach ausgedrückt, können Sie ein variables Argument verwenden, indem Sie "(* args, ** kwargs)" als Funktionsargument angeben.

Auf diese Weise kann der Dekorator für eine Funktion, die ein Argument akzeptiert, wie folgt geschrieben werden:

sample_013.py


def my_decorator_013(func):
    def inner( *args, **kwargs ):
        #Schreiben Sie hier den Pre-Call-Prozess
        ret = func( *args, **kwargs )
        #Schreiben Sie hier die Nachbearbeitung
        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()

Das Folgende ist das Ausführungsergebnis.

sample&nbsp;013


>>> def my_decorator_013(func):
...     def inner( *args, **kwargs ):
...         #Schreiben Sie hier den Pre-Call-Prozess
...         ret = func( *args, **kwargs )
...         #Schreiben Sie hier die Nachbearbeitung
...         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()
>>>

Ausnahmebehandlung

Berücksichtigen Sie auch Ausnahmen.

sample_014.py


def my_decorator_014(func):
    def inner( *args, **kwargs ):
        #Schreiben Sie hier den Pre-Call-Prozess
        try:
            ret = func( *args, **kwargs )
        except:
            raise
        #Schreiben Sie hier die Nachbearbeitung
        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      #Eine Nullteilungsausnahme tritt auf

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

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

Das Folgende ist das Ausführungsergebnis.

sample&nbsp;014


>>> def my_decorator_014(func):
...     def inner( *args, **kwargs ):
...         #Schreiben Sie hier den Pre-Call-Prozess
...         try:
...             ret = func( *args, **kwargs )
...         except:
...             raise
...         #Schreiben Sie hier die Nachbearbeitung
...         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      #Eine Nullteilungsausnahme tritt auf
...
>>> 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
>>>

Geben Sie das Argument an den Dekorateur weiter

Da ein Dekorateur eine Funktion (ein aufrufbares Objekt) ist, sollten Sie ihm ein Argument übergeben.

Die am Anfang zitierte Beschreibung der Funktionsdefinition enthielt auch ein Beispiel eines Dekorateurs mit Argumenten.

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

Da die Dekoratoren verschachtelt sind, sollten Sie der Einfachheit halber nur einen Dekorateur berücksichtigen.

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

Dies entspricht unten.

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

Es bedeutet, "_my_decorator (func)" mit der Funktion (angenommen _my_decorator) auszuführen, die durch Aufrufen von "my_decorator (" argument ")" zurückgegeben wird.

Die Verschachtelung wird einen Schritt tiefer, und die äußerste Funktion (Dekorator) erhält das Argument, und der vorhandene Dekorator ist darin enthalten.

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

Das Folgende ist das Ausführungsergebnis.

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('Streit')
... def f_015( arg1, arg2, arg3 ):
...     print( "in func( {}, {}, {} )".format(arg1, arg2, arg3) )
...
>>> f_015( "Argument 1", "Argument 2", "Argument 3" )
in inner, arg1=Streit, func=f_015
in func(Argument 1,Argument 2,Argument 3)
in inner leaving ...
>>>

Wenn Sie den Dekorator mit Argumenten aufrufen, werden der untere Teil arg1 und func in _my_decorator_body gespeichert.

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

Wenn Sie also f_015 () aufrufen, werden die gespeicherten Argumente arg1 und func referenziert.

Einmal zusammengefasst

Dekorateur ohne Argumente

Der Dekorateur, der kein Argument annimmt, ist wie folgt.

sample_014.py


def my_decorator_014(func):
    def inner( *args, **kwargs ):
        #Schreiben Sie hier den Pre-Call-Prozess
        try:
            ret = func( *args, **kwargs )
        except:
            raise
        #Schreiben Sie hier die Nachbearbeitung
        return ret
    return inner

Dekorateur, der ein Argument nimmt

Der Dekorateur, der das Argument übergibt, ist wie folgt.

sample_016.py


def my_decorator_016( arg1 ):
    def _my_decorator( func ):
        def inner( *args, **kwargs ):
            #Schreiben Sie hier den Pre-Call-Prozess
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            #Schreiben Sie hier die Nachbearbeitung
            return ret
        return inner
    return _my_decorator

Dekorateur, der das Vorhandensein oder Fehlen von Argumenten absorbiert

Was passiert, wenn Sie einen Dekorateur verwenden, der Argumente annimmt, ohne Argumente anzugeben?

sample_017.py


def my_decorator_017( arg1 ):
    def _my_decorator( func ):
        def inner( *args, **kwargs ):
            #Schreiben Sie hier den Pre-Call-Prozess
            try:
                ret = func( *args, **kwargs )
            except:
                raise
            #Schreiben Sie hier die Nachbearbeitung
            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" )

Ausführungsergebnis


$ 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
$

Dies liegt daran, dass wir keine Argumente an "@ my_decorator_017" übergeben haben, daher heißt es "f_017 = my_decorator_017 (f_017)". Es gibt zwei Möglichkeiten, dies zu vermeiden.

  1. Auch wenn Sie kein Argument für den Dekorateur angeben, müssen Sie () hinzufügen und es als Argument variabler Länge erhalten.
  2. Wenn für den Dekorateur kein Argument angegeben ist, wird ein einzelnes Funktionsobjekt übergeben und beurteilt.

Dies ist das erste Beispiel.

sample_018.py


def my_decorator_018( *args, **kwargs ):
    def _my_decorator( func ):
        def inner( *inner_args, **inner_kwargs ):
            #Schreiben Sie hier den Pre-Call-Prozess
            try:
                ret = func( *inner_args, **inner_kwargs )
            except:
                raise
            #Schreiben Sie hier die Nachbearbeitung
            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" )

Ausführungsergebnis


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

Die zweite Methode verwendet das erste Argument des Dekorateurs zur Beurteilung.

sample_019.py


def my_decorator_019( *args, **kwargs ):
    def _my_decorator( func ):
        def inner( *inner_args, **inner_kwargs ):
            #Schreiben Sie hier den Pre-Call-Prozess
            try:
                ret = func( *inner_args, **inner_kwargs )
            except:
                raise
            #Schreiben Sie hier die Nachbearbeitung
            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" )

Um zwischen den an den Dekorateur übergebenen Argumenten und den beim Aufruf von "f_019_ * ()" übergebenen Argumenten zu unterscheiden, werden sie durch "(* args, ** kwargs)" bzw. "(* inner_args, ** inner_kwargs)" unterschieden. tun.

Ausführungsergebnis


$ 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)

Jetzt, wo wir ein universelles Dekorations-Framework haben ... würde ich gerne denken ... Alle aufgerufenen Funktionsnamen (f_019_ * .__ name__) sind jetzt inner.

Endgültiges Ende

Sie können sehen, dass die Funktion "inner" anstelle der ursprünglichen Funktion aufgerufen wird. Die ursprüngliche Funktion kann sich jedoch auf Attribute wie Name (__name__) und Dokumentation ( __doc__) beziehen.

Es gibt einen Dekorateur namens "@ wraps", der dies vermeiden kann.

Siehe auch: [@ functools.wraps () --functools --- Manipulation von Funktionen höherer Ordnung und aufrufbaren Objekten - Python 3.8.1-Dokumentation](https://docs.python.org/ja/3/library/functools.html? Highlight = Wraps # functools.wraps) Siehe auch: [functools.update_wrapper () --functools --- Manipulieren von Funktionen höherer Ordnung und aufrufbaren Objekten - Python 3.8.1-Dokumentation](https://docs.python.org/ja/3/library/functools.html?highlight = wraps # functools.update_wrapper)

Wenn Sie dies vor dem "def inner" dekorieren, können Sie die Attribute des an die äußere Funktion ("_my_decorator ()") übergebenen Arguments "func" umbrechen und beibehalten.

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 ):
            #Schreiben Sie hier den Pre-Call-Prozess
            try:
                ret = func( *inner_args, **inner_kwargs )
            except:
                raise
            #Schreiben Sie hier die Nachbearbeitung
            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" )

Für "sample_019.py" wird "@wraps (func)" vor "from functools import wraps" und "def inner ()" hinzugefügt. func ist die Funktion, die als Argument an _my_decorator () übergeben wird.

Ausführungsergebnis


$ 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)
$

Der Name der Funktion (f_019_ * .__ name__) wird als Name der ursprünglichen Funktion anstelle von inner beibehalten.

Es war ein langer Weg, aber schließlich ist das Dekorations-Framework vollständig.

Da es generisch erstellt wurde, ist es "def my_decorator (* args, ** kwargs):", aber wenn die zu empfangenden Argumente festgelegt sind, ist es besser zu klären.

decorator_framework.py Full Version

decorator_framework.py&nbsp;Full&nbsp;Version


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

###########################################
#Dekorateur(decorator)
###########################################
from functools import wraps

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

    >>> @my_decorator
    ... def f1( arg1 ):
    ...     print( arg1 )
    ...
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
    No arguments
    _my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
    >>> @my_decorator('mytest1')
    ... def f2( arg2 ):
    ...     print( arg2 )
    ...
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
    There are some arguments: ('mytest1',)
    _my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
    >>> @my_decorator
    ... def f3( arg1 ):
    ...     print( arg1 )
    ...     a = 1/0
    ...
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
    No arguments
    _my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
    >>> @my_decorator('mytest2')
    ... def f4( arg2 ):
    ...     print( arg2 )
    ...     a = 1/0
    ...
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
    There are some arguments: ('mytest2',)
    _my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
    >>> try:
    ...     f1( "Hello, World! #1" )
    ... except:
    ...     print( "error #1" )
    ...
Die Vorverarbeitung erfolgt hier(<function f1 at 0x...>,) {} ('Hello, World! #1',) {}
    Hello, World! #1
Die Nachbearbeitung erfolgt hier... {} ('Hello, World! #1',) {}
    >>> try:
    ...         f2( "Hello, World! #2" )
    ... except:
    ...         print( "error #2" )
    ...
Die Vorverarbeitung erfolgt hier('mytest1',) {} ('Hello, World! #2',) {}
    Hello, World! #2
Die Nachbearbeitung erfolgt hier('mytest1',) {} ('Hello, World! #2',) {}
    >>> try:
    ...         f3( "Hello, World! #3" )
    ... except:
    ...         print( "error #3" )
    ...
Die Vorverarbeitung erfolgt hier(<function f3 at 0x...>,) {} ('Hello, World! #3',) {}
    Hello, World! #3
    error #3
    >>> try:
    ...         f4( "Hello, World! #4" )
    ... except:
    ...         print( "error #4" )
    ...
Die Vorverarbeitung erfolgt hier('mytest2',) {} ('Hello, World! #4',) {}
    Hello, World! #4
    error #4
    >>>
    """

    #Klicken Sie hier für Dekorateure, die eindeutig keine Argumente annehmen
    # _my_Dekorator umbenennen und global definieren
    def _my_decorator( func ):
        # _my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
        print( "_my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier" )
        @wraps(func)
        def _my_decorator_body( *body_args, **body_kwargs ):
            #Die Vorverarbeitung erfolgt hier
            print( "Die Vorverarbeitung erfolgt hier", args, kwargs, body_args, body_kwargs )
            try:
                #Ausführung des dekorierten Körpers
                ret = func( *body_args, **body_kwargs )
            except:
                raise
            #Die Nachbearbeitung erfolgt hier
            print( "Die Nachbearbeitung erfolgt hier", args, kwargs, body_args, body_kwargs )
            return ret

        #Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
        print( "Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2" )
        return _my_decorator_body

    #Dies ist das Ende des Dekorateurs, das eindeutig kein Argument annimmt

    #Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
    print( "Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1" )

    if len(args) == 1 and callable( args[0] ):
        #Wenn der Dekorateur ohne Argumente aufgerufen wird, behandeln Sie dies hier
        print( "No arguments" )
        return _my_decorator( args[0] )

    else:
        #Wenn der Dekorateur mit einem Argument aufgerufen wird, verarbeiten Sie es hier
        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,
            "Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1\n" +
            "No arguments\n" +
            "_my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier\n" +
            "Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2\n" +
            "Die Vorverarbeitung erfolgt hier(<function Test_My_Decorator.test_decorator_noarg.<locals>.t1 at 0x>,) {} ('test_decorator_noarg',) {}\n" +
            "test_decorator_noarg\n" +
            "Die Nachbearbeitung erfolgt hier(<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(),
            "Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1\n" +
            "There are some arguments: ('with arg',)\n" +
            "_my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier\n" +
            "Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2\n" +
            "Die Vorverarbeitung erfolgt hier('with arg',) {} ('test_decorator_witharg',) {}\n" +
            "test_decorator_witharg\n" +
            "Die Nachbearbeitung erfolgt hier('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()

Beispielausführung


$ python decorator_framework.py
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
No arguments
_my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
There are some arguments: ('mytest1',)
_my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
No arguments
_my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#1
There are some arguments: ('mytest2',)
_my_decorator_body()Wenn vor dem Definieren eine Verarbeitung erforderlich ist, schreiben Sie diese hier
Wenn bei der Beschreibung des Dekorateurs eine Verarbeitung erforderlich ist, schreiben Sie diese hier#2
Die Vorverarbeitung erfolgt hier(<function f1 at 0x76973a08>,) {} ('Hello, World! #1',) {}
Hello, World! #1
Die Nachbearbeitung erfolgt hier(<function f1 at 0x76973a08>,) {} ('Hello, World! #1',) {}
Die Vorverarbeitung erfolgt hier('mytest1',) {} ('Hello, World! #2',) {}
Hello, World! #2
Die Nachbearbeitung erfolgt hier('mytest1',) {} ('Hello, World! #2',) {}
Die Vorverarbeitung erfolgt hier(<function f3 at 0x7685bd20>,) {} ('Hello, World! #3',) {}
Hello, World! #3
error #3
Die Vorverarbeitung erfolgt hier('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