Python-Dekorateure: Wie und warum?

Ein Dekorateur übernimmt eine Funktion, fügt einige Funktionen hinzu und gibt sie zurück. In diesem Tutorial erfahren Sie, wie Sie einen Dekorateur erstellen und warum Sie ihn verwenden sollten.

Dekorateure in Python

Python verfügt über eine interessante Funktion namens Dekoratoren , mit der Sie einem vorhandenen Code Funktionen hinzufügen können.

Dies wird auch als Metaprogrammierung bezeichnet, da ein Teil des Programms versucht, einen anderen Teil des Programms zur Kompilierungszeit zu ändern.

Voraussetzungen für das Erlernen von Dekorateuren

Um die Dekorateure zu verstehen, müssen wir zunächst einige grundlegende Dinge in Python wissen.

Wir müssen uns damit abfinden, dass alles in Python (Ja! Sogar Klassen) Objekte sind. Von uns definierte Namen sind einfach Bezeichner, die an diese Objekte gebunden sind. Funktionen sind keine Ausnahmen, sie sind auch Objekte (mit Attributen). Verschiedene unterschiedliche Namen können an dasselbe Funktionsobjekt gebunden werden.

Hier ist ein Beispiel.

 def first(msg): print(msg) first("Hello") second = first second("Hello")

Ausgabe

 Hallo Hallo

Wenn Sie den Code ausführen, funktionieren beide Funktionen firstund secondgeben dieselbe Ausgabe aus. Hier beziehen sich die Namen firstund secondauf dasselbe Funktionsobjekt.

Jetzt werden die Dinge seltsamer.

Funktionen können als Argumente an eine andere Funktion übergeben werden.

Wenn Sie verwendeten Funktionen wie haben map, filterund reducein Python, dann wissen Sie bereits darüber.

Solche Funktionen, die andere Funktionen als Argumente verwenden, werden auch Funktionen höherer Ordnung genannt . Hier ist ein Beispiel für eine solche Funktion.

 def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result

Wir rufen die Funktion wie folgt auf.

 >>> operate(inc,3) 4 >>> operate(dec,3) 2

Darüber hinaus kann eine Funktion eine andere Funktion zurückgeben.

 def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()

Ausgabe

 Hallo

Hier is_returned()ist eine verschachtelte Funktion, die bei jedem Aufruf definiert und zurückgegeben wird is_called().

Schließlich müssen wir über Closures in Python Bescheid wissen.

Zurück zu den Dekorateuren

Funktionen und Methoden werden als aufrufbar bezeichnet, da sie aufgerufen werden können.

Tatsächlich wird jedes Objekt, das die spezielle __call__()Methode implementiert, als aufrufbar bezeichnet. Im einfachsten Sinne ist ein Dekorateur ein Callable, der einen Callable zurückgibt.

Grundsätzlich übernimmt ein Dekorateur eine Funktion, fügt einige Funktionen hinzu und gibt sie zurück.

 def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")

Wenn Sie die folgenden Codes in der Shell ausführen,

 >>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary

In dem oben gezeigten Beispiel make_pretty()ist ein Dekorateur. Im Zuweisungsschritt:

 pretty = make_pretty(ordinary)

Die Funktion ordinary()wurde dekoriert und die zurückgegebene Funktion erhielt den Namen pretty.

Wir können sehen, dass die Dekorationsfunktion der ursprünglichen Funktion einige neue Funktionen hinzugefügt hat. Dies ähnelt dem Verpacken eines Geschenks. Der Dekorateur fungiert als Hülle. Die Art des Objekts, das dekoriert wurde (tatsächliches Geschenk im Inneren), ändert sich nicht. Aber jetzt sieht es hübsch aus (seit es dekoriert wurde).

Im Allgemeinen dekorieren wir eine Funktion und weisen sie neu zu als:

 ordinary = make_pretty(ordinary).

Dies ist ein gängiges Konstrukt. Aus diesem Grund verfügt Python über eine Syntax, um dies zu vereinfachen.

Wir können das @Symbol zusammen mit dem Namen der Dekorationsfunktion verwenden und es über der Definition der zu dekorierenden Funktion platzieren. Beispielsweise,

 @make_pretty def ordinary(): print("I am ordinary")

ist äquivalent zu

 def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)

Dies ist nur ein syntaktischer Zucker, um Dekorateure zu implementieren.

Dekorationsfunktionen mit Parametern

Der obige Dekorator war einfach und funktionierte nur mit Funktionen, die keine Parameter hatten. Was wäre, wenn wir Funktionen hätten, die Parameter wie:

 def divide(a, b): return a/b

Diese Funktion hat zwei Parameter, a und b. Wir wissen, dass es einen Fehler gibt, wenn wir b als 0 übergeben.

 >>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero

Lassen Sie uns nun einen Dekorateur veranlassen, nach diesem Fall zu suchen, der den Fehler verursacht.

 def smart_divide(func): def inner(a, b): print("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)

Diese neue Implementierung wird zurückgegeben, Nonewenn die Fehlerbedingung auftritt.

 >>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide

Auf diese Weise können wir Funktionen dekorieren, die Parameter annehmen.

Ein aufmerksamer Beobachter wird feststellen, dass die Parameter der verschachtelten inner()Funktion im Dekorator mit den Parametern der Funktionen übereinstimmen, die er dekoriert. In Anbetracht dessen können wir jetzt allgemeine Dekorateure erstellen, die mit einer beliebigen Anzahl von Parametern arbeiten.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such a decorator will be:

 def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

 def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")

Output

 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************

The above syntax of,

 @star @percent def printer(msg): print(msg)

is equivalent to

 def printer(msg): print(msg) printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

 @percent @star def printer(msg): print(msg)

The output would be:

 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Interessante Beiträge...