Python Decorators

Table of Contents

 

Ein Decorator nimmt eine Funktion auf, fügt einige Funktionen hinzu und gibt sie zurück. In diesem Lernprogramm erfahren Sie, wie Sie einen Decorator erstellen können und warum Sie ihn verwenden sollten.

 

Decorators in Python

 

Python hat eine interessante Funktion namens Decorators, um einem bestehenden Code Funktionalität hinzuzufügen.

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

 


 

 

Prerequisites for learning decorators

 

Um Dekoratoren zu verstehen, müssen wir zunächst ein paar grundlegende Dinge in Python wissen.

Wir müssen uns mit der Tatsache anfreunden, dass alles in Python (Ja! Sogar Klassen), Objekte sind. Namen, die wir definieren, sind einfach Bezeichner, die an diese Objekte gebunden sind. Funktionen sind da keine Ausnahme, auch sie sind Objekte (mit Attributen). An ein und dasselbe Funktionsobjekt können verschiedene Namen gebunden werden.

Hier ist ein Beispiel.

def first(msg):
    print(msg)


first("Hello")

second = first
second("Hello")

 

Output

Hello
Hello

 

Wenn Sie den Code ausführen, werden beide Funktionen first und second ergeben die gleiche Ausgabe. Hier sind die Namen first und second beziehen sich auf das gleiche Funktionsobjekt.

Jetzt werden die Dinge etwas merkwürdiger.

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

Wenn Sie Funktionen wie map, filter und reduce in Python, dann wissen Sie das bereits.

 

Solche Funktionen, die andere Funktionen als Argumente annehmen, werden auch als higher order functions. 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

 

Außerdem 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()

 

Output

Hello

 

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

 

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

 


 

 

Getting back to Decorators

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

 

In der Tat kann jedes Objekt, das die spezielle __call__() Methode wird als callable bezeichnet. Im einfachsten Sinne ist ein Dekorator also ein Callable, das ein Callable zurückgibt.

Im Grunde genommen nimmt ein Decorator eine Funktion auf, fügt einige Funktionen hinzu und gibt sie zurück.

def make_pretty(func):
    def inner():
        print("Ich wurde dekoriert")
        func()
    return inner


def ordinary():
    print("Ich bin gewöhnlich")

 

When you run the following codes in shell,

>>> ordinary()
I am ordinary

>>> # Lassen Sie uns diese gewöhnliche Funktion decorate
>>> pretty = make_pretty(ordinary)
>>> pretty()
Ich wurde dekoriert
Ich bin gewöhnlich

 

Im oben gezeigten Beispiel, make_pretty() ist ein Decorator. Im Schritt der Zuweisung:

pretty = make_pretty(ordinary)

 

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

Wir können sehen, dass die Dekoratorfunktion der ursprünglichen Funktion einige neue Funktionen hinzugefügt hat. Dies ist ähnlich wie das Verpacken eines Geschenks. Der Dekorator fungiert als Hülle. Die Natur des Objekts, das dekoriert wurde (das eigentliche Geschenk darin), ändert sich nicht. Aber es sieht jetzt hübsch aus (da 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 und aus diesem Grund gibt es in Python eine Syntax, die dies vereinfacht.

 

Wir können die @ Symbol zusammen mit dem Namen der Dekoratorfunktion und platzieren Sie es oberhalb der Definition der zu dekorierenden Funktion. Zum Beispiel,

@make_pretty
def ordinary():
    print("Ich bin gewöhnlich")

 

ist äquivalent zu

def ordinary():
    print("Ich bin gewöhnlich")
ordinary = make_pretty(ordinary)

 

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

 


 

 

Decorating Functions with Parameters

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

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

 

Diese Funktion hat zwei Parameter, a und b. Wir wissen, dass es einen Fehler geben wird, wenn wir in b as 0.

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

 

Lassen Sie uns nun einen Decorator erstellen, der auf diesen Fall prüft, der den Fehler verursacht

def smart_divide(func):
    def inner(a, b):
        print("Divide", a, "und", b)
        if b == 0:
            print("kann nicht divide")
            return

        return func(a, b)
    return inner


@smart_divide
def divide(a, b):
    print(a/b)

 

Diese neue Implementierung liefert None wenn die Fehlerbedingung auftritt.

>>> divide(2,5)
Divide 2 und 5
0.4

>>> divide(2,0)
Divide 2 und 0
kann nicht divide

 

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

 

Einem aufmerksamen Beobachter wird auffallen, dass die Parameter der nested inner() Funktion innerhalb des Dekorators ist die gleiche wie die Parameter der Funktionen, die er dekoriert. Wenn wir dies berücksichtigen, können wir nun allgemeine Dekoratoren erstellen, die mit einer beliebigen Anzahl von Parametern arbeiten.

 

In Python wird diese Magie wie folgt ausgeführt function(*args, **kwargs). Auf diese Weise, args wird das Tupel  der Positionsargumente sein und kwargs wird das Wörterbuch der Schlüsselwortargumente sein. Ein Beispiel für einen solchen Dekorator ist:

def works_for_all(func):
    def inner(*args, **kwargs):
        print("Ich kann jede Funktion dekorieren")
        return func(*args, **kwargs)
    return inner

 


 

 

Chaining Decorators in Python

 

In Python können mehrere Decorators verkettet werden.

Das heißt, eine Funktion kann mehrfach mit verschiedenen (oder gleichen) Decorators dekoriert werden. Wir platzieren die Decorators einfach oberhalb der gewünschten Funktion.

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
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

 

Die obige Syntax von,

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

 

ist äquivalent zu

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

 

Die Reihenfolge, in der wir Dekoratoren verketten, ist wichtig. Wenn wir die Reihenfolge umgedreht hätten als,

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

 

Die Ausgabe würde sein:

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