EMZETT.
Login

Kurz: Ein Dekorator erweitert eine Funktion um zusätzliches Verhalten, ohne ihren Code zu ändern. Du schreibst ihn mit @name direkt über die Funktion.

Teil des Kurses Python

Kapitel 27 von 44 im Kurs Python. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Ein Dekorator erweitert eine Funktion um zusätzliches Verhalten, ohne ihren Code zu ändern. Du schreibst ihn mit @name direkt über die Funktion. Dekoratoren begegnen dir in der Praxis ständig, zum Beispiel in Webframeworks (@app.route) oder bei @cache.

Die Idee

Ein Dekorator ist eine Funktion, die eine Funktion bekommt und eine (meist neue) Funktion zurückgibt:

def laut(funktion):
    def verpackt(*args, **kwargs):
        ergebnis = funktion(*args, **kwargs)
        return ergebnis.upper() + "!"
    return verpackt
 
@laut
def gruss(name):
    return f"hallo {name}"
 
print(gruss("mia"))

Ausgabe:

HALLO MIA!

Die Zeile @laut ist nur eine Kurzschreibweise für gruss = laut(gruss).

Beispiel: Aufrufe zählen und Zeit messen

import time
 
def stoppe_zeit(funktion):
    def verpackt(*args, **kwargs):
        start = time.perf_counter()
        ergebnis = funktion(*args, **kwargs)
        dauer = time.perf_counter() - start
        print(f"{funktion.__name__} dauerte {'kurz' if dauer < 1 else 'lang'}")
        return ergebnis
    return verpackt
 
@stoppe_zeit
def rechne():
    return sum(range(100000))
 
print(rechne())

Ausgabe:

rechne dauerte kurz
4999950000

functools.wraps

Der verpackte Name und Docstring gehen verloren, wenn man nichts unternimmt. Mit functools.wraps bleiben sie erhalten:

from functools import wraps
 
def log(funktion):
    @wraps(funktion)
    def verpackt(*args, **kwargs):
        print(f"Aufruf von {funktion.__name__}{args}")
        return funktion(*args, **kwargs)
    return verpackt
 
@log
def addiere(a, b):
    """Addiert zwei Zahlen."""
    return a + b
 
print(addiere(2, 3))
print(addiere.__name__, "|", addiere.__doc__)

Ausgabe:

Aufruf von addiere(2, 3)
5
addiere | Addiert zwei Zahlen.

Dekoratoren mit Argumenten

Brauchst du Einstellungen, packst du eine weitere Funktion außen herum:

def wiederhole(n):
    def dekorator(funktion):
        def verpackt(*args, **kwargs):
            for _ in range(n):
                funktion(*args, **kwargs)
        return verpackt
    return dekorator
 
@wiederhole(3)
def sag(text):
    print(text)
 
sag("Hallo")

Ausgabe:

Hallo
Hallo
Hallo

Eingebaute Dekoratoren

DekoratorZweck
@functools.cacheErgebnisse zwischenspeichern
@staticmethodMethode ohne Zugriff auf Objekt oder Klasse
@classmethodMethode, die die Klasse bekommt
@propertyMethode wie ein Attribut ansprechen
@dataclassKlasse mit automatischem __init__ und mehr

Die letzten vier lernst du im Abschnitt über Klassen kennen.

Merke

  • Ein Dekorator nimmt eine Funktion und gibt eine erweiterte Funktion zurück
  • @dekorator über def ist dasselbe wie f = dekorator(f)
  • *args, **kwargs reichen beliebige Argumente durch
  • @functools.wraps erhält Name und Docstring
  • Für Einstellungen: Dekorator-Fabrik mit einer Funktion außen herum

Übungsaufgabe

Schreibe einen Dekorator nur_positiv, der eine Funktion nur aufruft, wenn alle Argumente größer als 0 sind, und sonst None zurückgibt.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Iteratoren und Generatoren

Weiter: Dateien lesen und schreiben

Alle Kapitel: Python im Überblick