Ein Dekorator ist eine Funktion, die eine Funktion umhüllt
Der Satz klingt abstrakt, aber die Mechanik ist einfach. Ein Dekorator nimmt eine Funktion entgegen und gibt eine Funktion zurück. Die zurückgegebene Funktion ruft normalerweise die ursprüngliche auf, mit etwas zusätzlichem Verhalten drumherum.
Das kürzeste mögliche Beispiel:
shout ist der Dekorator. Er nimmt eine Funktion (greet), baut eine neue Funktion (wrapper), die das Original aufruft und das Ergebnis in Großbuchstaben wandelt, und gibt sie zurück. Die Neuzuweisung greet = shout(greet) tauscht das Original gegen die umhüllte Version.
Dieses Neuzuweisungsmuster ist so häufig, dass Python ihm eine eigene Syntax gegeben hat.
Das @ ist Zucker für eine Neuzuweisung
@name auf der Zeile über einem def entspricht name = name(...) direkt nach der Funktionsdefinition:
@shout liest sich als „wende den Dekorator shout auf diese Funktion an“. Python führt direkt nach dem def greet = shout(greet) aus — gleiche Mechanik, weniger Tipperei.
Wenn du @name siehst, ersetz es gedanklich durch function = name(function). Mehr bedeutet die Syntax nicht.
Argumente verarbeiten
Die meisten Funktionen nehmen Argumente. Ein brauchbarer Dekorator reicht sie durch. Das Idiom ist *args, **kwargs — Pythons Art, beliebige Argumente zu akzeptieren —, weil der Wrapper sich nicht darum kümmern sollte, was die umhüllte Funktion erwartet:
*args fängt alle positionellen Argumente ab. **kwargs fängt alle Keyword-Argumente ab. Der Wrapper reicht alles unverändert an die umhüllte Funktion weiter und tut dann, wozu der Dekorator da ist — hier: das Ergebnis in Großbuchstaben wandeln.
Das ist die Form, die die meisten echten Dekoratoren annehmen.
Ein nützlicheres Beispiel: Zeitmessung
Gibt aus, wie lange eine Funktion dauert:
Das Muster — vor dem Aufruf etwas tun, nach dem Aufruf etwas tun — ist das, was die meisten Dekoratoren am Ende tun. Logging, Auth-Checks, Retries und Eingabevalidierung folgen alle derselben Form.
Die Originalidentität bewahren: functools.wraps
Eine Funktion zu dekorieren ersetzt sie, das heißt, die umhüllte Funktion verliert ihre ursprünglichen __name__- und __doc__-Attribute:
greet.__name__ ist jetzt "wrapper", und der Docstring ist weg. Das bricht help(), Tracebacks und jedes Werkzeug, das die Funktion inspiziert.
Die Lösung ist eine Zeile: @functools.wraps(func) an der inneren Funktion kopiert die Metadaten hinüber.
Setz immer @wraps(func) an die innere Funktion. Es kostet nichts und vermeidet spätere überraschende Debug-Sitzungen.
Dekoratoren mit Argumenten
Manchmal braucht der Dekorator selbst Konfiguration — „diese Funktion bis zu 3-mal wiederholen“, „mit Level DEBUG loggen“. Das heißt, eine Schicht mehr Verschachtelung: eine äußere Funktion, die die Argumente nimmt und einen Dekorator zurückgibt.
Drei Ebenen klingen nach viel. Lies von außen nach innen:
repeat(times=3)ist ein Funktionsaufruf. Er gibt dendecoratorzurück.decoratorist der eigentliche Dekorator — er nimmt eine Funktion und gibt eine umhüllte zurück.wrapperist die umhüllte Funktion, die zur Laufzeit läuft.
Diese Form steckt hinter @retry(times=5), @cache(maxsize=100) und Framework-Dekoratoren wie @app.route("/users"). Sobald du das Dreischicht-Muster siehst, lest sich die gesamte Familie gleich.
Dekoratoren stapeln
Du kannst mehr als einen Dekorator auf eine Funktion anwenden. Sie werden von unten nach oben gestapelt — der am nächsten am def läuft zuerst:
add_exclaim wickelt zuerst, fügt das ! hinzu. Dann wickelt shout darum und wandelt alles in Großbuchstaben. Die Ausgabe ist HI, ROSA!.
Die Reihenfolge zählt. Drehst du den Stapel um, bekommst du HI, ROSA! mit dem Ausrufezeichen nach der Großschreibung — visuell hier identisch, aber stell dir einen Dekorator vor, der JSON formatiert: ihn vor oder nach einem Dekorator laufen zu lassen, der Eingaben loggt, kann sehr unterschiedliche Ergebnisse erzeugen.
Eingebaute Dekoratoren, die du siehst
Python und seine Standardbibliothek liefern eine Handvoll Dekoratoren, denen du in echtem Code begegnest:
@propertymacht eine Methode zu einem berechneten Attribut.@staticmethodmarkiert eine Methode, die wederselfnochclsnutzt.@classmethodbekommt die Klasse alsclsstatt einer Instanz — ideal für alternative Konstruktoren.@functools.lru_cachememoisiert Ergebnisse, sodass wiederholte Aufrufe mit denselben Argumenten in einen Cache treffen.
Framework-Dekoratoren (@app.route, @pytest.fixture, @dataclass) folgen derselben Maschinerie. Nichts Besonderes — nur Funktionen, die Funktionen umhüllen.
Wann du einen schreibst — und wann nicht
Schreib einen Dekorator, wenn du dasselbe Verhalten auf viele Funktionen anwenden willst — Zeiterfassung, Logging, Retries, Autorisierungschecks. Der ganze Witz ist, dass das Verhalten aus dem Funktionsrumpf bleibt.
Lass den Dekorator weg, wenn:
- Das Verhalten zu einer bestimmten Funktion gehört. Schreib es in die Funktion.
- Du ihn nur für Tests willst. Ein Fixture oder Parameter ist klarer.
- Du versucht bist, vier oder fünf zu stapeln. An dem Punkt ist der Kontrollfluss in der Dekorator-Kette versteckt — eine Leserin muss jede Schicht abwickeln, um zu sehen, was tatsächlich läuft. Eine klare Hilfsfunktion liest sich oft besser.
Dekoratoren sind ein scharfes Werkzeug. Gut eingesetzt halten sie Code DRY und die Absicht offensichtlich. Schlecht eingesetzt verbergen sie, was das Programm tut. Bei der Frage „nutzen oder nicht“ lehn dich zu „offensichtlich“.
Als Nächstes: Type Hints
Dekoratoren sind ein häufiger Ort, an dem Type Hints in freier Wildbahn auftauchen — die wrapper-Funktionen annotieren oft ihre Signaturen. Type Hints sind ein kleines Feature, das sich schnell auszahlt, und kommen als Nächstes.
Häufig gestellte Fragen
Was ist ein Dekorator in Python?
Ein Dekorator ist eine Funktion, die eine andere Funktion nimmt und eine neue Funktion zurückgibt — meist eine, die die Originalfunktion mit zusätzlichem Verhalten umhüllt. Du wendest einen Dekorator mit @decorator_name auf der Zeile über einem def an. Die @-Syntax ist die Kurzform für func = decorator_name(func).
Wozu werden Dekoratoren in Python verwendet?
Verhalten rund um eine Funktion hinzuzufügen, ohne ihren Rumpf zu ändern — Logging, Zeiterfassung, Caching, Authentifizierungsprüfungen, Eingabevalidierung, Wiederholungen. Frameworks setzen sie stark ein: @app.route(...) in Flask, @pytest.fixture in pytest, @property und @staticmethod eingebaut.
Kann ich einen eigenen Dekorator schreiben?
Ja. Ein Dekorator ist einfach eine Funktion, die eine Funktion nimmt und eine Funktion zurückgibt. Die meisten eigenen Dekoratoren wickeln den ursprünglichen Aufruf in eine kleine innere Funktion, die vorher, nachher oder drumherum etwas tut. Nutz functools.wraps an der inneren Funktion, um Name und Docstring der Originalfunktion zu erhalten.