Einführung in Python-Dekorateure | Komentor

Einführung

Ich gebe zu, dass Dekorateure hart sind! Ein Teil des Codes, den Sie in diesem Tutorial sehen werden, wird zwangsläufig kompliziert sein. Die meisten Leute scheinen zumindest für eine Weile mit Dekorateuren zu kämpfen, also seien Sie nicht entmutigt, wenn Ihnen das komisch vorkommt. Aber dann können die meisten Menschen diesen Kampf überwinden. In diesem Tutorial werde ich Sie langsam durch den Prozess des Verständnisses von Dekorateuren führen. Ich gehe davon aus, dass Sie grundlegende Funktionen und grundlegende Klassen schreiben können. Wenn Sie diese Dinge nicht tun können, schlage ich vor, dass Sie lernen, wie es geht, bevor Sie hierher zurückkehren (es sei denn, Sie haben sich verlaufen, in diesem Fall sind Sie entschuldigt).

Ein Anwendungsfall: Timing-Funktionsausführung

Nehmen wir an, wir führen einen Codeabschnitt aus, dessen Ausführung etwas länger dauert, als wir möchten. Das Stück Code besteht aus einer Reihe von Funktionsaufrufen und wir sind davon überzeugt, dass mindestens einer dieser Aufrufe einen Engpass in unserem Code darstellt. Wie finden wir den Engpass? Eine Lösung, auf die wir uns jetzt konzentrieren werden, ist die zeitliche Ausführung von Funktionen.

Beginnen wir mit einem einfachen Beispiel. Wir haben nur eine Funktion, die wir timen möchten, func_a

def func_a(stuff):
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()

Eine Möglichkeit wäre, unseren Timing-Code um jeden Funktionsaufruf zu platzieren. Also das:

func_a(current_stuff)

wird etwas mehr so ​​aussehen:

before = datetime.datetime.now()
func_a(current_stuff)
after = datetime.datetime.now()
print "Elapsed Time = {0}".format(after-before)

Das wird gut funktionieren. Aber was passiert, wenn wir mehrere Anrufe haben func_a und wir wollen sie alle timen? Wir könnten jeden Anruf umstellen func_a mit unserem Timing-Code, aber das hat einen üblen Geruch. Es wäre bereit, den Zeitcode nur einmal zu schreiben. Anstatt es also außerhalb der Funktion zu platzieren, platzieren wir es innerhalb der Funktionsdefinition.

def func_a(stuff):
    before = datetime.datetime.now()
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()
    after = datetime.datetime.now()
    print "Elapsed Time = {0}".format(after-before)

Die Vorteile dieses Ansatzes sind:

  1. Wir haben den Code an einer Stelle, wenn wir ihn also ändern wollen (wenn wir zum Beispiel die verstrichene Zeit in einer Datenbank oder einem Protokoll speichern wollen), müssen wir ihn nur an einer Stelle ändern, anstatt bei jedem einzelnen Funktionsaufruf
  2. Wir müssen nicht daran denken, bei jedem Anruf vier statt einer Codezeile zu schreiben func_a das ist einfach eine rundum gute Sache

Okay, aber nur eine Funktion timen zu müssen, ist nicht so realistisch. Wenn Sie eine Sache timen müssen, besteht eine sehr gute Chance, dass Sie mindestens zwei Dinge timen müssen. Also gehen wir zu dritt

def func_a(stuff):
    before = datetime.datetime.now()
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()
    after = datetime.datetime.now()
    print "Elapsed Time = {0}".format(after-before)

def func_b(stuff):
    before = datetime.datetime.now()
    do_important_things_4()
    do_important_things_5()
    do_important_things_6()
    after = datetime.datetime.now()
    print "Elapsed Time = {0}".format(after-before)
    
def func_c(stuff):
    before = datetime.datetime.now()
    do_important_things_7()
    do_important_things_8()
    do_important_things_9()
    after = datetime.datetime.now()
    print "Elapsed Time = {0}".format(after-before)

Das sieht ziemlich fies aus. Was ist, wenn wir 8 Funktionen timen wollen? Dann entscheiden wir, dass wir die Timing-Informationen in einer Protokolldatei speichern möchten. Dann entscheiden wir, dass eine Datenbank besser ist. Yuck ist das Wort. Was wir hier brauchen, ist eine Möglichkeit, denselben Code in zu integrieren func_a, func_b und func_c auf eine Weise, bei der wir nicht überall Code einfügen müssen.

Ein kleiner Umweg: Funktionen, die Funktionen zurückgeben

Python ist dabei eine ziemlich spezielle Sprache Funktionen sind erstklassige Objekte. Das bedeutet, sobald eine Funktion in einem Gültigkeitsbereich definiert ist, kann sie an Funktionen übergeben, Variablen zugewiesen und sogar von Funktionen zurückgegeben werden. Diese einfache Tatsache macht Python-Dekorateure möglich. Sehen Sie sich den Code unten an und sehen Sie, ob Sie erraten können, was für die Zeilen mit den Bezeichnungen A, B, C und D passiert.

def get_function():
    print "inside get_function"                 
    def returned_function():                    
        print "inside returned_function"        
        return 1
    print "outside returned_function"
    return returned_function
   
returned_function()     
x = get_function()      
x                       
x()                     

EIN

Diese Zeile gibt uns a NameError und sagt das returned_function ist nicht vorhanden. Aber wir haben es gerade definiert, richtig? Was Sie hier wissen müssen, ist, dass es im Bereich von get_function definiert ist. Das heißt, innerhalb von get_function ist es definiert. Außerhalb von get_function ist es nicht. Wenn dies Sie verwirrt, versuchen Sie, mit dem zu spielen locals() Funktion ein wenig und informieren Sie sich über Python Scoping.

B

Dies druckt Folgendes:

inside get_function
outside returned_function

Python führt nichts im Inneren aus returned_function an dieser Stelle.

C

Diese Zeile gibt aus:

<function returned_function at 0x7fdc4463f5f0>

Das ist, xder Wert, der von zurückgegeben wird get_function()ist selbst eine Funktion.

Versuchen Sie erneut, die Linien B und C auszuführen. Beachten Sie, dass jedes Mal, wenn Sie diesen Vorgang wiederholen, die Adresse des zurückgegebenen returned_function ist anders. Jedes Mal get_function heißt es macht einen neuen returned function.

D

Seit x ist eine Funktion, sie kann aufgerufen werden. Berufung x ruft eine Instanz von auf returned_function. Was das ausgibt ist:

inside returned_function
1

Das heißt, es gibt die Zeichenfolge aus und gibt den Wert zurück 1.

Zurück zum Timing-Problem

Noch bei uns? Bist du nicht süß. Ok, also bewaffnet mit unserem neuen Wissen, wie lösen wir unser altes Problem? Ich würde vorschlagen, wir machen eine Funktion, nennen wir sie time_this, das eine andere Funktion als Parameter übernimmt und die Parameterfunktion in einen Timing-Code umschließt. Eine Kleinigkeit wie:

def time_this(original_function):                            
    def new_function(*args,**kwargs):                        
        before = datetime.datetime.now()                     
        x = original_function(*args,**kwargs)                
        after = datetime.datetime.now()                      
        print "Elapsed Time = {0}".format(after-before)      
        return x                                             
    return new_function()                                    

Ich gebe zu, es sieht irgendwie verrückt aus, also gehen wir es Zeile für Zeile durch:

1 Dies ist nur der Prototyp von time_this. time_this ist eine Funktion wie jede andere und hat einen Parameter.
2 Innen time_this Wir definieren eine Funktion. Jedes Mal time_this ausführt, wird eine neue Funktion erstellt.
3 Timing-Code, genau wie zuvor.
4 Wir rufen die ursprüngliche Funktion auf und bewahren das Ergebnis für später auf.
5,6 Der Rest des Timing-Codes.
7 Die new_function muss sich genauso verhalten wie die ursprüngliche Funktion und gibt daher das gespeicherte Ergebnis zurück.
8 Die Funktion erstellt in time_this wird endlich zurückgegeben.

Und jetzt wollen wir sicherstellen, dass unsere Funktionen zeitgesteuert sind:

def func_a(stuff):
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()
func_a = time_this(func_a)        

def func_b(stuff):
    do_important_things_4()
    do_important_things_5()
    do_important_things_6()
func_b = time_this(func_b)        
    
def func_c(stuff):
    do_important_things_7()
    do_important_things_8()
    do_important_things_9()
func_c = time_this(func_c)        

Anschauen func_awenn w ausführen func_a = time_this(func_a) wir ersetzen func_a mit der Funktion zurückgegeben von time_this. Also ersetzen wir func_A mit einer Funktion, die etwas Timing-Zeug macht (Zeile 3 oben), speichert das Ergebnis von func a in einer Variablen namens x (Zeile 4), macht ein bisschen mehr Timing-Zeug (Zeile 5 und 6) und gibt dann was auch immer zurück func_a wäre sowieso zurückgekehrt. Mit anderen Worten func_a wird immer noch auf die gleiche Weise aufgerufen und gibt dasselbe zurück, es wird nur auch zeitgesteuert. Ordentlich oder?

Wir stellen Dekorateure vor

Was wir gemacht haben, funktioniert gut und ist großartig und so, aber es ist hässlich und schwer zu lesen. Die netten Autoren von Python haben uns also eine andere und viel hübschere Art gegeben, es zu schreiben:

@time_this
def func_a(stuff):
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()

Ist genau gleichbedeutend mit:

def func_a(stuff):
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()
func_a = time_this(func_a)

Das wird gemeinhin als bezeichnet syntethischer Zucker. Daran ist nichts Magisches @. Es ist nur eine Konvention, auf die man sich geeinigt hat. Irgendwo auf der Linie wurde es entschieden.

Fazit

Ein Decorator ist also nur eine Funktion, die eine Funktion zurückgibt. Wenn das alles wie verrückt aussieht, dann stellen Sie sicher, dass die folgenden Themen für Sie sinnvoll sind, und kehren Sie dann zu diesem Tutorial zurück:

  • Python-Funktionen
  • Zielfernrohr
  • Python fungiert als erstklassige Objekte (vielleicht sogar Lookup-Lambda-Funktionen, dies könnte das Verständnis erleichtern).

Wenn Sie andererseits hungrig nach mehr sind, dann könnten Themen, die Sie interessieren könnten, interessant sein:

  • Dekorationskurse zB:

    @add_class_functionality
    class MyClass:
        ...
    
  • Dekorateure mit mehr Argumenten
    z.B:

    @requires_permission(name="edit")
    def save_changes(stuff):
       ...
    

Ich beabsichtige, fortgeschrittene Decorator-Themen in einem anderen Tutorial zu behandeln. Ich werde hier ein paar Links für euch setzen, sobald ich das getan habe.
Das Ende.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *