Python Decorators: Alles, was du wissen musst

Python Decorators sind eine der elegantesten und mächtigsten Funktionen in der Sprache. Sie erlauben es dir, Funktionen und Klassen auf eine saubere, wiederverwendbare und modulare Art zu erweitern – ohne den eigentlichen Code der Funktionen zu modifizieren. In diesem Blog-Post erfährst du alles über Python Decorators: von der Grundlagen bis hin zu praktischen Beispielen, häufigen Anwendungsfällen und typischen Problemen.


Was sind Python Decorators?

Ein Decorator ist eine Funktion, die eine andere Funktion als Argument entgegennimmt und diese Funktion erweitert oder modifiziert, ohne sie direkt zu verändern. Sie sind ein syntaktisches Zuckerstück in Python, das mit dem @-Zeichen verwendet wird.

Einfaches Beispiel

def my_decorator(func):
    def wrapper():
        print("Vor dem Aufruf der Funktion")
        func()
        print("Nach dem Aufruf der Funktion")
    return wrapper

@my_decorator
def say_hello():
    print("Hallo Welt!")

say_hello()

Ausgabe:

Vor dem Aufruf der Funktion
Hallo Welt!
Nach dem Aufruf der Funktion

Hier wird say_hello durch my_decorator erweitert – ohne den ursprünglichen Code zu verändern.


Typische Anwendungsfälle von Decorators

1. Zeitmessung (Timing)

Ein häufiger Anwendungsfall ist das Messen der Laufzeit einer Funktion:

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} dauerte {end - start:.4f} Sekunden")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "Fertig!"

slow_function()

2. Logging

Ein Decorator kann helfen, Funktionen zu loggen:

import functools

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Aufruf von {func.__name__} mit {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"Ergebnis: {result}")
        return result
    return wrapper

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

add(3, 5)

Ausgabe:

Aufruf von add mit (3, 5), {}
Ergebnis: 8

3. Authentifizierung (z. B. für Web-APIs)

def require_auth(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Simuliere eine Authentifizierung
        if not hasattr(wrapper, 'authenticated') or not wrapper.authenticated:
            raise PermissionError("Authentifizierung erforderlich")
        return func(*args, **kwargs)
    return wrapper

@require_auth
def sensitive_data():
    return "Vertrauliche Daten"

# Ohne Authentifizierung:
# sensitive_data()  # -> Fehler

Decorators mit Parametern

Manchmal will man einen Decorator auch mit Parametern verwenden. Das geht so:

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

@repeat(times=3)
def greet(name):
    print(f"Hallo {name}")

greet("Peter")

Ausgabe:

Hallo Peter
Hallo Peter
Hallo Peter

Wichtige Probleme und Fallstricke

1. Verlust von Funktionsmetadaten

Wenn du Decorators ohne functools.wraps schreibst, verlieren deine Funktionen Informationen wie __name__, __doc__, etc.

Problem:

def my_decorator(func):
    def wrapper():
        return func()
    return wrapper

@my_decorator
def example():
    """Das ist eine Beispiel-Funktion"""
    pass

print(example.__name__)  # Gibt "wrapper" aus, nicht "example"

Lösung:

Nutze @functools.wraps(func):

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper():
        return func()
    return wrapper

2. Decorators bei Klassen

Decorators funktionieren auch mit Klassen. Ein Beispiel für einen Klassendecorator:

def add_method(cls):
    def new_method(self):
        return "Neue Methode hinzugefügt!"
    cls.new_method = new_method
    return cls

@add_method
class MyClass:
    pass

obj = MyClass()
print(obj.new_method())  # "Neue Methode hinzugefügt!"

3. Reihenfolge von Decorators

Die Reihenfolge der Decorators ist wichtig. Sie werden von unten nach oben angewendet:

@decorator1
@decorator2
def my_function():
    pass

decorator2 wird zuerst auf my_function angewendet, dann decorator1.


Fazit

Python Decorators sind ein mächtiges Werkzeug, mit dem du Funktionen und Klassen elegant erweitern kannst. Sie sind besonders nützlich für:

  • Logging
  • Timing
  • Authentifizierung
  • Caching
  • Validierung

Doch Achtung: Mit großer Macht kommt auch große Verantwortung. Achte darauf, Metadaten mit functools.wraps zu bewahren und die Reihenfolge der Decorators zu verstehen.