# Dekoratoren in Python: was sie sind, wie sie funktionieren und wie Sie einen eigenen schreiben

URL: https://skillaria.top/de/guides/python/dekoratory-python

Ein Dekorator in Python ist eine Funktion, die eine andere Funktion annimmt und eine neue zurückgibt, meist eine Hülle, die vor oder nach dem Aufruf etwas ergänzt: Protokollierung, Zeitmessung, Zwischenspeicherung, Prüfung von Rechten. Die Zeile @decorator über def ist eine Kurzform für func = decorator(func); damit die Hülle Namen und Docstring der ursprünglichen Funktion behält, verwendet man functools.wraps.

Ein Dekorator in Python ist eine Funktion, die eine Funktion entgegennimmt und eine andere zurückgibt, in der Regel eine Hülle um das Original. So ergänzen Sie eine Funktion um Verhalten wie Protokollierung, Zeitmessung, Zwischenspeicher oder Wiederholungsversuche, ohne ihren Code zu ändern. Dekoratoren schreibt man mit der Zeile `@name` über der Funktionsdefinition, und man trifft sie überall an: in Flask und FastAPI, in pytest und in der Standardbibliothek.

## Was ein Dekorator einfach erklärt ist

Stellen Sie sich ein Geschenk mit Geschenkpapier vor. Das Geschenk (die Funktion) bleibt dasselbe, außen liegt aber eine Schicht, die zuerst reagiert. Ein Dekorator ist genau diese Verpackung: Beim Aufruf läuft zuerst der Code der Hülle. Sie ruft die ursprüngliche Funktion auf und kann mit dem Ergebnis etwas anfangen.

Das funktioniert, weil Funktionen in Python gewöhnliche Objekte sind. Man kann sie an andere Funktionen übergeben, aus Funktionen zurückgeben und innerhalb von Funktionen definieren. Auf diesen drei Eigenschaften beruhen Dekoratoren.

## Wie ein Dekorator funktioniert: @ ist syntaktischer Zucker

Schauen wir uns einen einfachen Dekorator an, der vor und nach dem Aufruf eine Meldung ausgibt:

```
def log_calls(func):
    def wrapper():
        print(f"Rufe {func.__name__} auf")
        result = func()
        print(f"{func.__name__} beendet")
        return result
    return wrapper

@log_calls
def hello():
    print("Hallo!")

hello()
```

```
Rufe hello auf
Hallo!
hello beendet
```

Die Zeile `@log_calls` über `def` ist gleichbedeutend mit dem folgenden Ausdruck nach der Definition:

```
def hello():
    print("Hallo!")

hello = log_calls(hello)
```

Der Name `hello` verweist jetzt auf `wrapper`. Die ursprüngliche Funktion lebt innerhalb des Closures weiter, denn `wrapper` erinnert sich an die Variable `func` aus der äußeren Funktion. Wichtig ist: Der Dekorator wird nur einmal angewendet, beim Ausführen von `def`, also beim Import des Moduls. Die Hülle dagegen läuft bei jedem Aufruf.

## Closures und Dekoratoren

Ein Closure ist eine innere Funktion, die sich Variablen der äußeren Funktion merkt, auch nachdem diese beendet ist. Ohne Closures gäbe es keine Dekoratoren: So erhält `wrapper` Zugriff auf `func`. Im Closure kann auch ein Zustand gespeichert werden, etwa ein Zähler für Aufrufe:

```
def count_calls(func):
    calls = 0
    def wrapper(*args, **kwargs):
        nonlocal calls
        calls += 1
        print(f"{func.__name__}: Aufruf Nr. {calls}")
        return func(*args, **kwargs)
    return wrapper
```

## Einen Dekorator in Python schreiben: das universelle Muster

Das erste Beispiel hat zwei Schwächen: Die Hülle nimmt keine Argumente entgegen, und sie überschreibt den Namen der ursprünglichen Funktion. Das folgende Muster sollten Sie als Ausgangspunkt für jeden Dekorator verwenden:

```
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Code vor dem Aufruf
        result = func(*args, **kwargs)
        # Code nach dem Aufruf
        return result
    return wrapper
```

Drei Regeln des Musters:

1. **`*args, **kwargs`** — die Hülle nimmt beliebige Argumente entgegen und reicht sie unverändert weiter.
2. **`return result`** — das Ergebnis muss zurückgegeben werden, sonst liefert die dekorierte Funktion immer `None`.
3. **`@functools.wraps(func)`** — überträgt die Metadaten der ursprünglichen Funktion auf die Hülle.

Ein Beispiel für einen Dekorator, der die Laufzeit misst:

```
import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} dauerte {elapsed:.3f} s")
        return result
    return wrapper

@timer
def slow_sum(n):
    return sum(range(n))

slow_sum(10_000_000)   # slow_sum dauerte 0.142 s
```

## Wozu functools.wraps dient

Ohne `wraps` werden Name, Docstring und andere Attribute der dekorierten Funktion ersetzt, nach außen sichtbar ist dann `wrapper`:

```
def plain(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@plain
def add(a, b):
    """Addiert zwei Zahlen."""
    return a + b

print(add.__name__, add.__doc__)   # wrapper None
```

Mit `@functools.wraps(func)` bekommt die Hülle `__name__`, `__doc__`, `__module__`, `__qualname__` und `__annotations__` der Originalfunktion, und im Attribut `__wrapped__` steht ein Verweis auf das Original. Das ist wichtig für Fehlersuche und Tracebacks, für `help()`, für Frameworks, die Funktionen über ihren Namen registrieren, und für `inspect.signature`, das die echte Signatur anzeigt.

## Dekorator mit Parametern

Manchmal braucht ein Dekorator Einstellungen, zum Beispiel `@retry(times=3)` oder `@app.route("/")`. Dafür fügt man eine weitere Verschachtelungsebene hinzu. Die äußere Funktion nimmt die Parameter entgegen und gibt den eigentlichen Dekorator zurück. Diese Konstruktion heißt Dekorator-Fabrik.

```
import functools

def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def ping():
    print("ping")

ping()   # ping, ping, ping — drei Zeilen
```

Die Reihenfolge der Aufrufe: `repeat(times=3)` liefert `decorator`, dieser wird auf `ping` angewendet und gibt `wrapper` zurück. Anders gesagt: `@repeat(times=3)` bedeutet `ping = repeat(times=3)(ping)`.

Ein praktisches Beispiel ist die Wiederholung bei einem Fehler:

```
import functools
import time

def retry(times=3, delay=1.0, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    if attempt == times:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(times=5, delay=0.5, exceptions=(ConnectionError,))
def fetch_rate():
    ...
```

## Mehrere Dekoratoren auf einer Funktion

Dekoratoren lassen sich übereinander stapeln. Angewendet werden sie von unten nach oben: Der Dekorator, der dem `def` am nächsten steht, kommt zuerst.

```
@timer
@log_calls
def report():
    ...

# dasselbe:
report = timer(log_calls(report))
```

Beim Aufruf läuft die äußere Schicht (`timer`) zuerst, deshalb misst sie auch die Laufzeit von `log_calls`. Die Reihenfolge ist wichtig, etwa wenn ein Dekorator Zugriffsrechte prüft und ein anderer Ergebnisse zwischenspeichert.

## Eingebaute und Standard-Dekoratoren in Python

Viele nützliche Dekoratoren gibt es bereits in der Sprache und in der Standardbibliothek:

| Dekorator | Was er macht |
| --- | --- |

| `@property` | macht eine Methode zu einem Attribut nur zum Lesen; mit `@x.setter` wird daraus ein kontrolliertes Attribut |

| `@staticmethod` | Methode ohne `self`, eine gewöhnliche Funktion im Namensraum der Klasse |

| `@classmethod` | die Methode erhält die Klasse `cls` statt einer Instanz — so schreibt man alternative Konstruktoren |

| `@functools.cache`, `@functools.lru_cache` | speichern Ergebnisse nach Argumenten zwischen |

| `@functools.wraps` | kopiert die Metadaten einer Funktion auf die Hülle |

| `@dataclasses.dataclass` | erzeugt `__init__`, `__repr__` und `__eq__` für eine Klasse |

| `@contextlib.contextmanager` | macht aus einem Generator einen Kontextmanager für `with` |

### Zwischenspeichern: functools.cache und lru_cache

Ein Cache-Dekorator merkt sich das Ergebnis für jede Kombination von Argumenten und liefert es bei einem erneuten Aufruf ohne Neuberechnung zurück. Ein klassisches Beispiel sind rekursive Fibonacci-Zahlen, die ohne Cache exponentiell lange brauchen:

```
import functools

@functools.cache
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

print(fib(100))   # 354224848179261915075
```

`functools.cache` (seit Python 3.9) speichert alle Ergebnisse ohne Begrenzung. `@functools.lru_cache(maxsize=128)` behält nur die letzten Einträge und verdrängt ältere, was für lange laufende Dienste sicherer ist. Die Statistik zeigt `fib.cache_info()`, geleert wird der Cache mit `fib.cache_clear()`. Argumente solcher Funktionen müssen hashbar sein: Eine Liste als Argument führt zu `TypeError: unhashable type: 'list'`. Außerdem sollte man nur reine Funktionen cachen, also solche, die bei gleichen Argumenten immer dasselbe Ergebnis liefern und keine Nebenwirkungen haben.

### Was der Dekorator property macht

`@property` erlaubt, auf eine Methode wie auf ein Attribut zuzugreifen, ohne Klammern, und bei Bedarf das Zuweisen zu kontrollieren:

```
class Account:
    def __init__(self, balance):
        self._balance = balance

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("Der Kontostand darf nicht negativ sein")
        self._balance = value

acc = Account(100)
print(acc.balance)   # 100
acc.balance = -5     # ValueError
```

### staticmethod und classmethod

```
class Money:
    def __init__(self, kopecks):
        self.kopecks = kopecks

    @classmethod
    def from_rubles(cls, rubles):
        return cls(round(rubles * 100))

    @staticmethod
    def is_valid(value):
        return value >= 0

m = Money.from_rubles(12.5)
print(m.kopecks, Money.is_valid(-1))   # 1250 False
```

Gerade `@classmethod` erlaubt es, mit der Klasse selbst zu arbeiten statt mit ihren Instanzen: `from_rubles` erzeugt korrekt auch ein Objekt einer Unterklasse.

## Dekoratoren für Klassen und Klassen als Dekoratoren

Einen Dekorator kann man auch auf eine Klasse anwenden: Er erhält die Klasse und gibt eine Klasse zurück, dieselbe oder eine veränderte. So funktioniert `@dataclass`. Ein einfaches Beispiel ist ein Register von Klassen:

```
registry = {}

def register(cls):
    registry[cls.__name__] = cls
    return cls

@register
class CsvExporter:
    pass

print(registry)   # {'CsvExporter': }
```

Umgekehrt kann auch eine Klasse als Dekorator dienen. Geeignet ist jedes Objekt mit einer Methode `__call__`. Den Zustand speichert man bequem in Attributen der Instanz:

```
import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.calls = 0

    def __call__(self, *args, **kwargs):
        self.calls += 1
        return self.func(*args, **kwargs)

@CountCalls
def tick():
    pass

tick(); tick()
print(tick.calls)   # 2
```

## Asynchroner Dekorator

Für Funktionen mit `async def` muss die Hülle ebenfalls eine Koroutine sein und das Ergebnis mit `await` abwarten. Andernfalls wird ein nicht ausgeführtes Koroutinenobjekt zurückgegeben:

```
import functools

def log_async(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        print(f"Start {func.__name__}")
        result = await func(*args, **kwargs)
        print(f"Fertig {func.__name__}")
        return result
    return wrapper
```

## Wozu Dekoratoren in Python dienen

- **Querschnittslogik** — Protokollierung, Metriken, Zeitmessung, ohne Code in jede Funktion zu kopieren.
- **Zwischenspeicherung** — `@functools.cache` für teure reine Berechnungen.
- **Prüfungen** — Zugriffsrechte, Validierung von Argumenten, Autorisierung in Web-Handlern.
- **Registrierung** — Routen in Flask und FastAPI (`@app.get("/")`), Fixtures in pytest.
- **Zuverlässigkeit** — Wiederholungsversuche, Timeouts, Fehlerbehandlung.

Der größte Vorteil ist die Trennung der Verantwortlichkeiten: Eine Funktion erledigt ihre Aufgabe, und Zusatzverhalten wird mit einer Zeile hinzugefügt und genauso leicht wieder entfernt.

## Typische Fehler

- **Die Hülle gibt das Ergebnis nicht zurück** — die Funktion liefert nach dem Dekorieren `None`.
- **Keine `*args, **kwargs`** — `TypeError` beim Aufruf mit Argumenten.
- **`wraps` vergessen** — falsche `__name__` und Docstrings, Verwirrung in den Logs.
- **Klammern bei der Fabrik fehlen** — `@repeat` statt `@repeat(3)`: Die Funktion landet im Parameter `times`.
- **Schwerer Code im Körper des Dekorators** — er läuft beim Import des Moduls, nicht beim Aufruf.

## Fazit

Dekoratoren in Python sind Funktionen höherer Ordnung: Sie nehmen eine Funktion entgegen und geben eine Hülle zurück, und das `@` ist nur eine kurze Schreibweise für eine Zuweisung. Beginnen Sie mit dem Muster aus `*args, **kwargs`, `return result` und `functools.wraps`, und für Einstellungen fügen Sie eine Fabrikebene hinzu. Funktionen, Closures und Annotationen, auf denen Dekoratoren beruhen, werden im Python-Kurs praktisch durchgearbeitet.

## FAQ

**Kann man einen Dekorator wieder von einer Funktion entfernen?**

Wenn der Dekorator functools.wraps verwendet, ist das Original über das Attribut __wrapped__ erreichbar: func.__wrapped__(...) ruft die Funktion ohne die Hülle auf. Sonst bleibt nur, die Zeile mit @ aus dem Code zu entfernen.

**Wann läuft der Code eines Dekorators?**

Der Körper des Dekorators selbst läuft einmal, bei der Definition der Funktion, meist beim Import des Moduls. Der Code innerhalb von wrapper läuft bei jedem Aufruf der dekorierten Funktion.

**Kann man Methoden einer Klasse dekorieren?**

Ja, ein gewöhnlicher Dekorator mit *args, **kwargs funktioniert auch bei Methoden. self steht dann einfach als erstes Element in args.

**Worin unterscheidet sich ein Dekorator von Vererbung?**

Vererbung verändert das Verhalten einer Klasse über eine Hierarchie. Ein Dekorator umhüllt eine bestimmte Funktion oder Klasse von außen und lässt sich ohne Änderung des Codes mit anderen Dekoratoren kombinieren.


---
Source: Skillaria.Top (https://skillaria.top)
