Dekoratoren

Funktionen können auch als Argumente an andere Funktionen übergeben werden und die Ergebnisse anderer Funktionen zurückgegeben. So ist es z.B. möglich, eine Python-Funktion zu schreiben, die eine andere Funktion als Parameter annimmt, sie in eine andere Funktion einbettet, die etwas Ähnliches tut, und dann die neue Funktion zurückgibt. Diese neue Kombination kann dann anstelle der ursprünglichen Funktion verwendet werden:

 1 >>> def inf(func):
 2 ...     print("Information about", func.__name__)
 3 ...     def details(*args):
 4 ...         print("Execute function", func.__name__, "with the argument(s)")
 5 ...         return func(*args)
 6 ...     return details
 7 ...
 8 >>> def my_func(*params):
 9 ...     print(params)
10 ...
11 >>> my_func = inf(my_func)
12 Information about my_func
13 >>> my_func("Hello", "Pythonistas!")
14 Execute function my_func with the argument(s)
15 ('Hello', 'Pythonistas!')
Zeile 2

Die inf-Funktion gibt den Namen der Funktion, die sie umhüllt, aus.

Zeile 6

Wenn sie fertig ist, gibt die inf-Funktion die umhüllte Funktion zurück.

Ein Dekorator ist syntaktischer Zucker für diesen Prozess und ermöglicht euch, eine Funktion mit einem einzeiligen Zusatz in eine andere zu packen. Ihr erhaltet immer noch genau den gleichen Effekt wie beim vorherigen Code, aber der resultierende Code ist viel sauberer und leichter zu lesen. Die Verwendung eines Dekorators besteht ganz einfach aus zwei Teilen:

  1. der Definition der Funktion, die andere Funktionen umhüllen oder dekorieren soll, und

  2. der Verwendung eines @, gefolgt von dem Dekorator, unmittelbar bevor die umhüllte Funktion definiert wird.

Die Dekorfunktion sollte eine Funktion als Parameter annehmen und eine Funktion zurückgeben, wie folgt:

1 >>> @inf
2 ... def my_func(*params):
3 ...     print(params)
4 ...
5 Information about my_func
6 >>> my_func("Hello", "Pythonistas!")
7 Execute function my_func with the argument(s)
8 ('Hello', 'Pythonistas!')
Zeile 1

Die Funktion my_func wird mit @inf dekoriert.

Zeile 7

Die umhüllte Funktion wird aufgerufen, nachdem die Dekorator-Funktion fertig ist.

functools

Das Python-functools-Modul ist für Funktionen höherer Ordnung gedacht, also Funktionen, die auf andere Funktionen wirken oder diese zurückgeben. Meist könnt ihr sie als Dekoratoren verwenden, so u.a.:

functools.cache()

Einfacher, leichtgewichtiger, Funktionscache ab Python ≥ 3.9, der manchmal auch memoize genannt wird. Er gibt dasselbe zurück wie functools.lru_cache() mit dem Parameter maxsize=None, wobei zusätzlich ein Dictionaries mit den Funktionsargumenten erstellt wird. Da alte Werte nie gelöscht werden müssen, ist diese Funktion dann auch kleiner und schneller. Ein Beispiel:

1>>> from functools import cache
2>>> @cache
3... def factorial(n):
4...     return n * factorial(n-1) if n else 1
5...
6>>> factorial(8)
740320
8>>> factorial(10)
93628800
Zeile 6

Da es kein zuvor gespeichertes Ergebnis gibt, werden neun rekursive Aufrufe gemacht.

Zeile 8

macht nur zwei neue Aufrufe, da die anderen Ergebnisse aus dem Zwischenspeicher kommen.

functools.wraps()

Dieser Dekorator lässt die Wrapper-Funktion so, so wie die ursprüngliche Funktion aussehen mit ihren Namen und ihren Eigenschaften.

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwargs):
...         """Wrapper docstring"""
...         print('Call decorated function')
...         return f(*args, **kwargs)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Example docstring"""
...     print('Call example function')
...
>>> example.__name__
'example'
>>> example.__doc__
'Example docstring'

Ohne @wraps-Dekorator wäre stattdessen Name und Docstring der wrapper-Methode zurückgegeben worden:

>>> example.__name__
'wrapper'
>>> example.__doc__
'Wrapper docstring'