2.23. Dekoratoren

Ein Dekorator ist ein Stück Syntax, das eine Funktion (oder eine Methode) in eine andere Funktion einhüllt. Der Wrapper kann vor und nach dem ursprünglichen Aufruf Verhalten hinzufügen, den Rückgabewert ersetzen oder Metadaten anhängen. Die Form ist:

@wrapper
def f():
    ...

Die Zeile @wrapper ist äquivalent zu f = wrapper(f) – die Funktion f wird normal erstellt, dann an wrapper übergeben, und das Ergebnis wird erneut an den Namen f gebunden.

Eine Funktion, die links in eine "Wrapper"-Box eintritt und rechts als dekorierte Funktion wieder austritt.

Ein Dekorator nimmt eine Funktion entgegen und gibt eine neue Funktion zurück.

2.23.1. Eingebaute Methoden-Dekoratoren

Einige Dekoratoren werden mit Python ausgeliefert und innerhalb von Klassenrümpfen verwendet.

2.23.1.1. @property

Verwandelt eine Methode in ein berechnetes Attribut. Der Aufrufer greift darauf zu, als wäre es ein einfaches Attribut (ohne Klammern), aber bei jedem Lesezugriff läuft eine Methode:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

Verwenden Sie es, wenn ein Attribut, das einfach aussieht, eine winzige Berechnung dahinter benötigt. Wenn die Arbeit aufwendig ist, ziehen Sie eine reguläre Methode vor – Aufrufer erwarten nicht, dass Attributzugriffe langsam sind.

2.23.1.2. @classmethod

Definiert eine Methode, die als erstes Argument die Klasse statt einer Instanz erhält. Der erste Parameter wird üblicherweise cls genannt:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

Klassenmethoden sind die Standardmethode, um alternative Konstruktoren bereitzustellen – Factory-Funktionen, die eine auf nicht-standardmäßige Weise erstellte Instanz zurückgeben.

2.23.1.3. @staticmethod

Definiert eine Methode, die weder die Instanz noch die Klasse erhält – sie ist einfach eine gewöhnliche Funktion, die aus organisatorischen Gründen im Namensraum der Klasse lebt:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

Verwenden Sie es sparsam; wenn eine Funktion wirklich nichts mit dem Zustand der Klasse zu tun hat, ist eine einfache Funktion auf Modulebene meist sauberer.

2.23.2. Einen eigenen Dekorator schreiben

Ein Dekorator ist eine Funktion, die eine Funktion entgegennimmt und eine Funktion zurückgibt. Die minimale Form:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

Ausgabe:

calling add

Der wrapper schließt über func und leitet alles an ihn weiter. *args / **kwargs ermöglicht es, mit jeder Funktion zu arbeiten, nicht nur mit zweiargumentigen. Dieses Muster ist die Grundlage anspruchsvollerer Dekoratoren (Zeitmessung, Caching, Wiederholung bei Fehler), aber der Kern ist immer derselbe: eine Funktion entgegennehmen, eine Funktion zurückgeben.