Python Decorators: Funktionsweise, Beispiele und Best Practices

Bei der Entwicklung realer Python-Anwendungen gibt es häufig Logik, die in vielen verschiedenen Funktionen benötigt wird. Typische Beispiele dafür sind Logging, Authentifizierung, Validierung, Zeitmessung oder die Überwachung der Performance. API-Endpunkte müssen beispielsweise möglicherweise überprüfen, ob ein Benutzer authentifiziert ist, während bei performancekritischen Funktionen die Ausführungszeit gemessen werden soll.

Wird dieselbe zusätzliche Logik direkt in jede einzelne Funktion geschrieben, kann der Code schnell unübersichtlich werden. Dadurch verschlechtern sich Lesbarkeit und Wartbarkeit. Decorators lösen dieses Problem, indem solche übergreifenden Aufgaben in wiederverwendbare Komponenten ausgelagert werden, die sich einheitlich auf Funktionen anwenden lassen. In Frameworks wie Flask verknüpft beispielsweise der Decorator @app.route("/") eine URL mit einer Funktion, ohne dass die Routing-Logik direkt innerhalb der Funktion implementiert werden muss. In Django können Decorators wie @login_required den Zugriff auf authentifizierte Benutzer beschränken. Dadurch wird die Anwendung modularer, übersichtlicher und einfacher zu strukturieren.

Die wichtigsten Erkenntnisse zu Python Decorators

  • Python Decorators ermöglichen es, Funktionen um zusätzliche Funktionalität zu erweitern, ohne den ursprünglichen Funktionscode verändern zu müssen.
  • Decorators reduzieren wiederholten Code und verbessern die Wiederverwendbarkeit.
  • Die Syntax @decorator_name bietet eine übersichtliche Möglichkeit, Funktionen zu umschließen.
  • Typische Einsatzgebiete sind Logging, Authentifizierung, Caching, Validierung und Performance-Monitoring.
  • *args und **kwargs ermöglichen flexible Decorators für Funktionen mit unterschiedlichen Argumenten.
  • functools.wraps erhält die Metadaten der ursprünglichen Funktion und gilt als bewährte Vorgehensweise.
  • Mehrere Decorators können kombiniert werden, um unterschiedliche Funktionalitätsschichten hinzuzufügen.
  • Frameworks wie Flask und Django verwenden Decorators intensiv für Routing, Authentifizierung und die Verarbeitung von Anfragen.
  • Decorators sollten möglichst einfach bleiben und sich auf eine klar definierte Aufgabe konzentrieren, damit der Code verständlich und gut zu debuggen bleibt.
  • Das Verständnis von Decorators ist wichtig, um übersichtliche und wartbare Python-Anwendungen zu entwickeln.

Ein Decorator lässt sich grundsätzlich als Wrapper um eine Funktion verstehen. Die eigentliche Funktion bleibt bestehen, während der Decorator zusätzliches Verhalten vor, nach oder rund um diese Funktion ergänzt.

Das grundlegende Prinzip von Python Decorators

Betrachten wir zunächst eine einfache Funktion:

def greet():
    print("Hello, world!")

Angenommen, vor und nach jeder Funktion soll eine zusätzliche Zeile ausgegeben werden, ohne dass jede einzelne Funktion dafür verändert werden muss. Genau hierfür kann ein Decorator verwendet werden:

def my_decorator(func):
    def wrapper():
        print("--- Before ---")
        func()           # calls the original function
        print("--- After ---")
    return wrapper

@my_decorator
def greet():
    print("Hello, world!")

greet()

Ausgabe:

--- Before ---
Hello, world!
--- After ---

Die Schreibweise @my_decorator ist lediglich eine kürzere Form von greet = my_decorator(greet). Python ersetzt die referenzierte Funktion automatisch durch die umschlossene Variante. Ein praktisches Beispiel dafür ist ein Decorator, der misst, wie lange eine Funktion für ihre Ausführung benötigt:

import time

def timer(func):
    def wrapper(*args, **kwargs):        # *args lets it work with ANY function
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    return wrapper

@timer
def slow_task():
    time.sleep(1)
    print("Task done!")

slow_task()

Ausgabe:

Task done!
slow_task took 1.0012 seconds

Warum Python Decorators in realen Projekten wichtig sind

Decorators sind im gesamten Python-Ökosystem weit verbreitet. Häufige Beispiele sind:

  • @staticmethod / @classmethod — integrierte Python-Decorators für Methoden innerhalb von Klassen.
  • @app.route('/home') — Webframeworks verwenden solche Decorators zur Definition von Routen.
  • @login_required — Django verwendet diesen Decorator, um Seiten auf authentifizierte Benutzer zu beschränken.
  • Logging, Caching und Wiederholungen fehlgeschlagener Anfragen — auch solche Aufgaben lassen sich übersichtlich mit Decorators lösen.

Zusammengefasst nimmt ein Decorator eine Funktion entgegen, ergänzt zusätzliches Verhalten und gibt anschließend eine neue beziehungsweise umschlossene Funktion zurück, ohne dass der ursprüngliche Funktionscode verändert werden muss.

Wie Python Decorators intern funktionieren

Um Decorators besser zu verstehen, sind zunächst einige grundlegende Konzepte von Python wichtig.

Grundlage: Funktionen sind in Python Objekte

Python behandelt Funktionen als Objekte. Dadurch können Funktionen ähnlich wie andere Werte, beispielsweise Ganzzahlen oder Zeichenketten, verwendet und weitergegeben werden.

def say_hello():
    print("Hello!")

# Pass a function as an argument
def run_it(func):
    func()

run_it(say_hello)   # prints: Hello!

# Assign a function to a variable
my_func = say_hello
my_func()           # prints: Hello!

# Return a function from another function
def get_greeter():
    def say_hi():
        print("Hi!")
    return say_hi   # returning the function, not calling it

greeter = get_greeter()
greeter()           # prints: Hi!

Dieses Verhalten bildet die grundlegende technische Basis für Decorators.

Warum werden Decorators benötigt?

Angenommen, ein Projekt enthält zahlreiche Funktionen und für jede dieser Funktionen soll dasselbe Logging ausgeführt werden.

Ohne Decorators

def add(a, b):
    print("Function started")
    result = a + b
    print("Function ended")
    return result

def multiply(a, b):
    print("Function started")
    result = a * b
    print("Function ended")
    return result

Diese Vorgehensweise bringt mehrere Nachteile mit sich:

  • Der gleiche Code wird mehrfach geschrieben.
  • Mit zunehmender Projektgröße wird die Wartung schwieriger.
  • Wenn sich das Logging ändert, müssen sämtliche betroffenen Funktionen angepasst werden.

Decorators lösen dieses Problem, indem gemeinsame Funktionalität nur einmal definiert und anschließend wiederverwendet wird.

Mit Decorators

Mithilfe eines Decorators können wiederholte Anweisungen wie "Function started" und "Function ended" in eine einzige wiederverwendbare Komponente ausgelagert werden. Dadurch müssen diese Zeilen nicht mehr in jeder einzelnen Funktion stehen.

Schritt 1: Decorator erstellen

def log_function(func):

    def wrapper(a, b):
        print("Function started")

        result = func(a, b)

        print("Function ended")

        return result

    return wrapper

Schritt 2: Decorator anwenden

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


@log_function
def multiply(a, b):
    return a * b

Die Funktionen aufrufen

print(add(2, 3))
print(multiply(4, 5))

Ausgabe:

Function started
Function ended
5

Function started
Function ended
20

Was hat sich verändert?

Die eigentlichen Funktionen enthalten jetzt ausschließlich ihre zentrale Logik:

und:

Das zusätzliche Verhalten für das Logging wird getrennt davon durch den Decorator übernommen.

Visuelles Verständnis des Ablaufs

Wird diese Funktion ausgeführt:

hat Python intern sinngemäß zuvor Folgendes ausgeführt:

Der tatsächliche Ablauf lässt sich daher folgendermaßen darstellen:

wrapper()
    ├── print("Function started")
    ├── call original add()
    ├── print("Function ended")
    └── return result

Flexiblere Decorators mit *args und **kwargs

Der vorherige Decorator funktioniert ausschließlich mit Funktionen, die genau zwei Argumente erhalten. Eine allgemeinere Variante kann stattdessen *args und **kwargs verwenden:

def log_function(func):

    def wrapper(*args, **kwargs):
        print("Function started")

        result = func(*args, **kwargs)

        print("Function ended")

        return result

    return wrapper

Diese Variante unterstützt:

  • eine beliebige Anzahl an Argumenten
  • Positionsargumente
  • Schlüsselwortargumente

Warum Decorators so leistungsfähig sind

Angenommen, in einem Projekt benötigen 100 Funktionen dasselbe Logging. Ohne Decorators bedeutet dies:

  • die Logging-Logik muss im gesamten Projekt immer wieder geschrieben werden

Mit Decorators hingegen:

  • wird das Logging nur einmal implementiert
  • kann dieselbe Implementierung überall wiederverwendet werden

Diese Möglichkeit, gemeinsame Funktionalität zentral zu definieren, ist einer der Hauptgründe dafür, dass Decorators in realen Python-Projekten und Frameworks häufig verwendet werden, beispielsweise in:

  • Flask
  • Django
  • FastAPI
  • PyTorch
  • TensorFlow

Praktische Beispiele für Python Decorators

Die folgenden Beispiele zeigen einige der häufigsten Einsatzmöglichkeiten für Decorators in kleinen Projekten ebenso wie in produktiven Anwendungen.

1. Zeitmessung und Performance-Messung

Diese Variante eignet sich zum Profiling langsamer Funktionen oder zum Benchmarking bestimmter Codeabschnitte.

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} ran in {end - start:.4f}s")
        return result
    return wrapper

@timer
def process_data(n):
    total = sum(range(n))
    return total

process_data(1_000_000)
# process_data ran in 0.0312s

Für kurze Zeitmessungen ist perf_counter() in der Regel besser geeignet als time.time(). Die Funktion bietet eine höhere Auflösung und wird nicht von Änderungen der Systemuhr beeinflusst.

2. Logging

Anstatt Logging-Anweisungen an vielen verschiedenen Stellen einzubauen, kann ein Logging-Decorator diese Aufgabe zentral übernehmen.

import logging
from functools import wraps

logging.basicConfig(level=logging.INFO)

def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"Calling {func.__name__} | args={args} kwargs={kwargs}")
        result = func(*args, **kwargs)
        logging.info(f"{func.__name__} returned {result}")
        return result
    return wrapper

@log_calls
def multiply(a, b):
    return a * b

multiply(4, 5)
# INFO: Calling multiply | args=(4, 5) kwargs={}
# INFO: multiply returned 20

In einer produktiven Umgebung kann logging.info durch eine strukturierte Logging-Lösung wie structlog oder ein anderes zentrales Logging-System ersetzt werden.

3. Wiederholungsversuche bei Fehlern

Retry-Decorators sind besonders nützlich für Netzwerkoperationen, API-Anfragen und andere Vorgänge, die vorübergehend fehlschlagen können.

import time
from functools import wraps

def retry(times=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Attempt {attempt} failed: {e}")
                    if attempt < times:
                        time.sleep(delay)
            raise Exception(f"{func.__name__} failed after {times} attempts")
        return wrapper
    return decorator

@retry(times=3, delay=2)
def fetch_data(url):
    import requests
    response = requests.get(url, timeout=5)
    response.raise_for_status()
    return response.json()

fetch_data("https://api.example.com/data")
# Attempt 1 failed: Connection timeout
# Attempt 2 failed: Connection timeout
# Attempt 3 failed: Connection timeout
# Exception: fetch_data failed after 3 attempts

Dieses Beispiel verwendet eine sogenannte Decorator Factory. Der Ausdruck retry(times=3) gibt zunächst den eigentlichen Decorator zurück. Durch diese zusätzliche Ebene können Decorators eigene Konfigurationsargumente erhalten.

4. Caching und Memoization

Caching verhindert, dass aufwendige Berechnungen erneut durchgeführt werden müssen, wenn ein zuvor berechnetes Ergebnis bereits vorhanden ist.

from functools import wraps

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
            print(f"Cache miss — computing for {args}")
        else:
            print(f"Cache hit for {args}")
        return cache[args]
    return wrapper

@memoize
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

fibonacci(6)
# Cache miss — computing for (6,)
# Cache miss — computing for (5,)
# ...
fibonacci(6)
# Cache hit for (6,)   ← instantly returns stored result

Python enthält bereits eine für produktive Anwendungen geeignete Implementierung dieses Prinzips:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

lru_cache steht für Least Recently Used Cache. Die Implementierung ist threadsicher und entfernt ältere Einträge, sobald die konfigurierte Größe des Caches erreicht wird. In realen Projekten ist diese Lösung normalerweise einer selbst implementierten Cache-Variante vorzuziehen.

5. Zugriffskontrolle und Autorisierung

Decorators zur Autorisierung sind ein häufig verwendetes Muster in Webframeworks wie Flask und Django.

from functools import wraps

def require_role(role):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get("role") != role:
                raise PermissionError(f"Access denied. Required role: {role}")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_user(user, user_id):
    print(f"Deleting user {user_id}")

admin = {"name": "Shaoni", "role": "admin"}
guest = {"name": "Guest", "role": "viewer"}

delete_user(admin, 42)    # Deleting user 42
delete_user(guest, 42)    # PermissionError: Access denied. Required role: admin

Django-Decorators wie @login_required und @permission_required verwenden dieses allgemeine Muster, um Zugriffsregeln durchzusetzen.

6. Eingabevalidierung

Mit einem Decorator lassen sich Argumente überprüfen, bevor sie die eigentliche Logik einer Funktion erreichen.

from functools import wraps

def validate_positive(*arg_positions):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for i in arg_positions:
                if args[i] <= 0:
                    raise ValueError(
                        f"Argument at position {i} must be positive, got {args[i]}"
                    )
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_positive(0, 1)
def calculate_area(width, height):
    return width * height

calculate_area(5, 10)    # 50
calculate_area(-3, 10)   # ValueError: Argument at position 0 must be positive

7. Rate Limiting

Die Begrenzung, wie häufig eine Funktion innerhalb eines bestimmten Zeitraums ausgeführt werden darf, ist besonders bei API-Clients ein häufiges Anwendungsgebiet.

import time
from functools import wraps

def rate_limit(calls_per_second=1):
    min_interval = 1.0 / calls_per_second
    last_called = [0.0]   # mutable container to hold state in closure

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            elapsed = time.time() - last_called[0]
            wait = min_interval - elapsed
            if wait > 0:
                print(f"Rate limit: waiting {wait:.2f}s")
                time.sleep(wait)
            last_called[0] = time.time()
            return func(*args, **kwargs)
        return wrapper
    return decorator

@rate_limit(calls_per_second=2)
def call_api(endpoint):
    print(f"Calling {endpoint}")

call_api("/users")
call_api("/posts")    # Rate limit: waiting 0.49s
call_api("/comments") # Rate limit: waiting 0.49s

Python Decorators im Überblick

Decorator Anwendungsfall Praktischer Einsatz
@timer Ausführungszeit messen Profiling, Benchmarking
@log_calls Funktionsaufrufe protokollieren Observability, Debugging
@retry Vorübergehende Fehler behandeln API-Clients, Datenbankverbindungen
@lru_cache Aufwendige Ergebnisse zwischenspeichern ML-Inferenz, Datenbankabfragen
@require_role Endpunkte nach Benutzerrolle schützen Authentifizierung in Django und Flask
@validate_positive Eingaben frühzeitig validieren Datenpipelines, APIs
@rate_limit Aufrufhäufigkeit begrenzen Externe API-Clients

Reale Einsatzmöglichkeiten von Decorators in Frameworks

Moderne Python-Frameworks setzen Decorators intensiv ein, da sie eine übersichtliche und wiederverwendbare Möglichkeit bieten, zusätzliche Funktionalität mit einer Anwendung zu verbinden, ohne deren zentrale Geschäftslogik zu verändern. Frameworks wie Flask und Django verwenden Decorators unter anderem für:

  • Routing
  • Authentifizierung
  • Autorisierung
  • Caching
  • Validierung von Anfragen
  • Beschränkung von HTTP-Methoden
  • Logging

Diese Muster sorgen dafür, dass Anwendungen übersichtlicher, besser wartbar und strukturell klarer bleiben.

Routing mit einem Flask Decorator

Ein häufig verwendetes Beispiel für einen Decorator findet sich beim Routing mit Flask:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
   return "Homepage"

In diesem Beispiel ist:

ein Decorator. Er weist Flask an, die Funktion home() auszuführen, wenn ein Benutzer die Adresse / aufruft.

Authentifizierung mit einem Flask Decorator

Auch Authentifizierung ist ein typischer Einsatzbereich für Decorators. Ein Beispiel:

@app.route("/dashboard")
@login_required
def dashboard():
   return "Dashboard"

Hier überprüft:

ob der Benutzer angemeldet ist, bevor der Zugriff auf das Dashboard zugelassen wird.

Warum dieses Prinzip nützlich ist

Ohne Decorators müsste die Authentifizierungsprüfung direkt innerhalb jeder geschützten Funktion wiederholt werden. Zum Beispiel:

def dashboard():
   if not logged_in:
       return "Please log in"
   return "Dashboard"

Der Einsatz von Decorators:

  • reduziert wiederholten Code
  • hält Routendefinitionen übersichtlich
  • zentralisiert die Logik für die Authentifizierung

Besonders in größeren Anwendungen mit zahlreichen geschützten Routen bietet dieses Prinzip deutliche Vorteile.

Authentifizierung mit einem Django Decorator

Auch Django verwendet Decorators in vielen Bereichen. Zum Beispiel:

from django.contrib.auth.decorators import login_required
@login_required
def dashboard(request):
   return HttpResponse("Welcome")

Der Decorator @login_required sorgt dafür, dass:

  • nur authentifizierte Benutzer die View aufrufen können
  • nicht authentifizierte Benutzer zur Anmeldeseite weitergeleitet werden

Vorteile

  • Wiederverwendbare Sicherheitsprüfungen
  • Übersichtlichere View-Funktionen
  • Bessere Wartbarkeit
  • Zentrale Verwaltung der Authentifizierung

HTTP-Methoden mit Django Decorators beschränken

Django bietet Decorators, mit denen festgelegt werden kann, welche HTTP-Methoden eine View akzeptiert.

Beispiel:

from django.views.decorators.http import require_POST
@require_POST
def submit(request):
   return HttpResponse("Submitted")

Der Decorator:

beschränkt die Funktion auf POST-Anfragen. Wird stattdessen beispielsweise eine GET-Anfrage gesendet, gibt Django automatisch einen Fehler zurück.

Warum diese Beschränkung wichtig ist

Dieses Prinzip hilft dabei:

  • API-Regeln durchzusetzen
  • die Sicherheit zu verbessern
  • ungültige Anfragearten zu verhindern
  • die Validierungslogik zu vereinfachen

Ohne einen entsprechenden Decorator müssten vergleichbare Prüfungen direkt innerhalb jeder betroffenen Funktion implementiert werden.

Caching mit einem Django Decorator

Decorators können außerdem zur Optimierung der Performance eingesetzt werden.

Beispiel:

from django.views.decorators.cache import cache_page
@cache_page(60)
def my_view(request):
   return HttpResponse("Cached")

Hier sorgt:

dafür, dass die erzeugte Antwort für 60 Sekunden im Cache gespeichert wird. Ruft ein weiterer Benutzer während dieses Zeitraums dieselbe Seite auf:

  • liefert Django die zwischengespeicherte Antwort aus
  • muss die zugrunde liegende Funktion nicht erneut ausgeführt werden

Fortgeschrittene Konzepte für Python Decorators

Nachdem die grundlegenden Prinzipien verstanden wurden, geht es im nächsten Schritt um Muster, die bei Decorators in produktiven Python-Anwendungen häufig eingesetzt werden. Fortgeschrittene Techniken lösen praktische Probleme wie den Erhalt von Funktionsmetadaten, die Konfiguration von Decorators über Argumente und die Kombination mehrerer Decorators.

Solche Techniken werden häufig in Frameworks, Bibliotheken und größeren Python-Anwendungen verwendet.

Funktionsmetadaten mit functools.wraps erhalten

Ein häufiges Problem bei Decorators besteht darin, dass die Wrapper-Funktion die ursprüngliche Funktion ersetzt. Dadurch können wichtige Metadaten wie der Funktionsname, der Dokumentationsstring, Annotationen und Informationen für das Debugging verloren gehen.

Betrachten wir den folgenden Decorator:

def decorator(func):

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

   return wrapper

Dieser wird anschließend auf eine Funktion angewendet:

@decorator
def greet():
   """This function greets the user"""
   print("Hello")

Nun wird der Funktionsname überprüft:

Ausgabe:

Python liefert "wrapper" anstelle von "greet", weil die Metadaten der ursprünglichen Funktion durch die Wrapper-Funktion ersetzt wurden. Das kann unter anderem Probleme verursachen bei:

  • Debugging
  • Logging
  • API-Dokumentation
  • Introspection
  • Testframeworks

Python stellt functools.wraps bereit, um dieses Problem zu lösen.

functools.wraps verwenden

from functools import wraps

def decorator(func):

   @wraps(func)
   def wrapper(*args, **kwargs):
       return func(*args, **kwargs)

   return wrapper

Der Decorator wird erneut angewendet:

@decorator
def greet():
   """This function greets the user"""
   print("Hello")

Nun:

Ausgabe:

Der Decorator @wraps(func) übernimmt die Metadaten der ursprünglichen Funktion für die Wrapper-Funktion. Bei der Entwicklung von Decorators für produktive Anwendungen gilt diese Vorgehensweise als Best Practice.

Decorators mit Argumenten

In vielen praktischen Anwendungsfällen benötigen Decorators zusätzliche Konfigurationswerte. Dafür muss ein Decorator eigene Argumente akzeptieren können. Dieses Muster führt zu einer weiteren Ebene verschachtelter Funktionen.

Beispiel:

def repeat(n):

   def decorator(func):

       def wrapper(*args, **kwargs):

           for _ in range(n):
               func(*args, **kwargs)

       return wrapper

   return decorator

Der Decorator wird folgendermaßen eingesetzt:

@repeat(3)
def greet():
   print("Hello")

Aufruf:

Ausgabe:

Aufbau eines Decorators mit Argumenten verstehen

Dieses Beispiel besteht aus drei Funktionen:

repeat()        → accepts decorator arguments
decorator()     → accepts the original function
wrapper()       → executes additional logic

Die daraus resultierende Transformation lautet:

Dieses Muster wird häufig verwendet für:

  • Retry-Mechanismen
  • Caching-Systeme
  • Rate Limiting
  • Autorisierungsmechanismen
  • Logging-Systeme
  • Timeout-Verarbeitung

Ein Retry-Decorator kann beispielsweise die gewünschte Anzahl an Wiederholungsversuchen erhalten:

Ein Caching-Decorator könnte stattdessen eine Ablaufzeit akzeptieren:

Durch solche Argumente werden Decorators deutlich flexibler und vielseitiger einsetzbar.

Mehrere Decorators kombinieren

Python erlaubt es, mehrere Decorators auf dieselbe Funktion anzuwenden.

Beispiel:

@decorator_one
@decorator_two
def func():
   pass

Intern entspricht dies:

func = decorator_one(decorator_two(func))

Die Reihenfolge der Decorators ist dabei von Bedeutung.

Python wendet Decorators von unten nach oben an:

  1. decorator_two umschließt zunächst die ursprüngliche Funktion.
  2. decorator_one umschließt anschließend das daraus entstandene Ergebnis.

Beispiel für verkettete Decorators

def decorator_one(func):

   def wrapper():
       print("Decorator One - Before")

       func()

       print("Decorator One - After")

   return wrapper


def decorator_two(func):

   def wrapper():
       print("Decorator Two - Before")

       func()

       print("Decorator Two - After")

   return wrapper

Beide Decorators werden anschließend auf dieselbe Funktion angewendet:

@decorator_one
@decorator_two
def greet():
   print("Hello")

Aufruf:

Ausgabe:

Decorator One - Before
Decorator Two - Before
Hello
Decorator Two - After
Decorator One - After

Ablauf mehrerer Decorators verstehen

Die daraus entstehende Struktur des Funktionsaufrufs lautet:

decorator_one(
   decorator_two(
       greet
   )
)

Dadurch entstehen mehrere verschachtelte Ausführungsebenen. Jeder Decorator kann zusätzliches Verhalten vor oder nach der umschlossenen Funktion ausführen. Diese Verkettung von Decorators wird in Frameworks häufig verwendet. Eine Webroute kann beispielsweise gleichzeitig folgende Funktionen erhalten:

  • Authentifizierung
  • Caching
  • Rate Limiting
  • Logging

Beispiel:

@app.route("/dashboard")
@login_required
@cache_page(60)
def dashboard():
   return "Dashboard"

Jeder Decorator ergänzt eine eigene Funktionalitätsschicht, während die eigentliche Anwendungslogik klar davon getrennt bleibt.

Häufig gestellte Fragen zu Python Decorators

1. Welche Fehler machen Anfänger bei Decorators besonders häufig?

Ein häufiger Fehler besteht darin, innerhalb der Wrapper-Funktion *args und **kwargs nicht zu verwenden.

Falsches Beispiel:

def decorator(func):

    def wrapper():
        return func()

    return wrapper

Diese Implementierung funktioniert nur mit Funktionen, die keine Argumente erwarten.

Eine flexiblere Variante lautet:

def decorator(func):

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

    return wrapper

Ein weiterer häufiger Fehler besteht darin, zwar die ursprüngliche Funktion aufzurufen, deren Rückgabewert jedoch nicht zurückzugeben:

def decorator(func):

    def wrapper(*args, **kwargs):
        func(*args, **kwargs)

    return wrapper

In dieser Variante führt der Wrapper zwar die Funktion aus, verwirft jedoch deren Ergebnis. Stattdessen sollte der Rückgabewert weitergegeben werden:

def decorator(func):

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

    return wrapper

Auch der Verlust von Metadaten ist ein wichtiges Thema. Ohne functools.wraps kann eine dekorierte Funktion ihren ursprünglichen Namen, ihren Docstring und für das Debugging relevante Informationen verlieren.

2. Warum ist functools.wraps bei Decorators wichtig?

Wenn ein Decorator eine Funktion umschließt, ersetzt die Wrapper-Funktion technisch die ursprüngliche Funktion. Ohne weitere Maßnahmen können dadurch unter anderem folgende Metadaten verloren gehen:

  • Funktionsname
  • Docstrings
  • Annotationen
  • Debugging-Informationen

Mit functools.wraps bleiben diese ursprünglichen Metadaten erhalten.

Beispiel:

from functools import wraps

def decorator(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper

Bei Decorators für produktive Python-Anwendungen gilt diese Vorgehensweise allgemein als Best Practice.

3. Können mehrere Decorators auf dieselbe Funktion angewendet werden?

Ja. Python ermöglicht es, mehrere Decorators miteinander zu verketten.

Beispiel:

@decorator_one
@decorator_two
def greet():
    print("Hello")

Python wendet die Decorators von unten nach oben an. Intern entspricht das Beispiel:

greet = decorator_one(decorator_two(greet))

Dieses Muster wird in Frameworks wie Flask und Django häufig verwendet, wenn beispielsweise Authentifizierung, Caching, Logging und Validierung kombiniert werden sollen.

4. Wann sollten Decorators nicht verwendet werden?

Decorators sind leistungsfähig, aber nicht für jede Aufgabe die beste Lösung. Werden sehr kleine oder einfache Funktionen durch zusätzliche Decorators abstrahiert, kann dadurch unnötige Komplexität entstehen.

Beispiel:

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

Wenn das zusätzliche Verhalten nur sehr gering ist, kann es verständlicher sein, die entsprechende Logik direkt in die Funktion zu schreiben. Decorators können außerdem das Debugging erschweren, da der Ablauf durch mehrere Wrapper-Schichten führt. In kleinen Skripten oder einfachen Einsteigerprojekten kann der übermäßige Einsatz von Decorators zu unnötigem Over-Engineering führen. Besonders sinnvoll sind Decorators dann, wenn dieselbe Funktionalität für mehrere Funktionen wiederverwendet werden soll.

5. Welche Best Practices gelten für Python Decorators?

Ein Decorator sollte möglichst einfach bleiben und sich auf eine klar definierte Aufgabe konzentrieren. Ein gut strukturierter Decorator:

  • erfüllt eine eindeutige Aufgabe
  • verwendet verständliche Bezeichnungen
  • erhält die Metadaten der ursprünglichen Funktion
  • vermeidet unnötig tiefe Verschachtelungen

Aussagekräftige Namen machen den Zweck eines Decorators unmittelbar verständlich:

@login_required
@cache_page(60)
@retry(3)

Die jeweiligen Namen vermitteln unmittelbar, welche Aufgabe jeder Decorator übernimmt.

Auch functools.wraps sollte in den meisten Decorator-Implementierungen standardmäßig verwendet werden. Sehr lange und tief verschachtelte Decorator-Ketten sollten dagegen vermieden werden, weil sie die Lesbarkeit verringern und die Fehlersuche erschweren können.

6. Warum werden Decorators in Frameworks so häufig eingesetzt?

Decorators helfen Frameworks dabei, die eigentliche Anwendungslogik von Infrastrukturaufgaben zu trennen. Ein Beispiel:

@app.route("/")
@login_required
def dashboard():
    return "Dashboard"

Die Funktion selbst kann sich ausschließlich auf die eigentliche Anwendungslogik konzentrieren, während Decorators zusätzliche Aufgaben übernehmen, darunter:

  • Routing
  • Authentifizierung
  • Caching
  • Validierung von Anfragen
  • Berechtigungen

Diese Trennung führt zu übersichtlicheren und leichter wartbaren Anwendungen.

7. Sind Decorators langsamer als normale Funktionen?

Decorators verursachen einen kleinen zusätzlichen Aufwand, da eine oder mehrere Wrapper-Funktionen ausgeführt werden müssen. In den meisten Anwendungen ist dieser Performance-Unterschied jedoch gering. Die Vorteile durch eine übersichtlichere Architektur und wiederverwendbare Logik überwiegen normalerweise den zusätzlichen Funktionsaufruf. Sehr lange Decorator-Ketten sollten in besonders performancekritischen Systemen dennoch sorgfältig geplant werden.

8. Können Decorators Funktionsargumente oder Rückgabewerte verändern?

Ja. Ein Decorator kann Argumente und Rückgabewerte überprüfen, validieren, verändern oder vollständig ersetzen.

Beispiel:

def uppercase(func):

    def wrapper():
        result = func()

        return result.upper()

    return wrapper

Verwendung:

@uppercase
def greet():
    return "hello"

Ausgabe:

Diese Fähigkeit macht Decorators unter anderem nützlich für:

  • Validierung
  • Formatierung
  • Serialisierung
  • Caching
  • Datentransformation

9. Was ist der Unterschied zwischen einem Decorator und einer normalen Funktion?

Eine normale Funktion führt direkt eine bestimmte Aufgabe aus. Ein Decorator dagegen verändert oder erweitert das Verhalten einer anderen Funktion, ohne dass deren ursprünglicher Quellcode angepasst werden muss.

Beispiel:

def greet():
    print("Hello")

Diese Funktion führt lediglich ihre definierte Logik aus. Ein Decorator kann zusätzliches Verhalten um diese Logik legen und dadurch dieselbe übergreifende Funktionalität für mehrere unterschiedliche Funktionen wiederverwendbar machen.

10. Können Decorators nur auf Funktionen angewendet werden?

Nein. Decorators lassen sich ebenfalls anwenden auf:

  • Klassen
  • Methoden
  • statische Methoden
  • Properties

Python selbst enthält beispielsweise folgende integrierte Decorators:

@property
@staticmethod
@classmethod

Diese eingebauten Decorators verändern jeweils unterschiedliche Aspekte des Verhaltens von Klassen und deren Methoden.

11. Warum verbessern Decorators die Wartbarkeit von Code?

Decorators verschieben wiederkehrende Funktionalität in wiederverwendbare Komponenten. Ohne Decorators können Aufgaben wie Logging oder Authentifizierung über zahlreiche Funktionen hinweg mehrfach implementiert werden. Mit Decorators:

@log_function
@login_required

muss die gemeinsame Funktionalität nur einmal implementiert werden und lässt sich anschließend überall dort verwenden, wo sie benötigt wird. Dadurch wird doppelter Code reduziert, Änderungen lassen sich einfacher durchführen und größere Anwendungen werden besser wartbar.

Fazit zu Python Decorators

Python Decorators bieten eine übersichtliche und flexible Möglichkeit, Funktionen um zusätzliche Funktionalität zu erweitern, ohne ihre ursprüngliche Implementierung verändern zu müssen. Sie reduzieren wiederholten Code, verbessern die Wiederverwendbarkeit und erleichtern die Wartung von Anwendungen. Die Einsatzmöglichkeiten reichen von einfachem Logging bis hin zu fortgeschrittenen Mustern, wie sie beispielsweise in Frameworks wie Flask und Django genutzt werden. Wer versteht, wie Decorators funktionieren, kann Python-Code übersichtlicher, skalierbarer und langfristig besser wartbar gestalten.

Quelle: digitalocean.com

Jetzt 200€ Guthaben sichern

Registrieren Sie sich jetzt in unserer ccloud³ und erhalten Sie 200€ Startguthaben für Ihr Projekt.

Das könnte Sie auch interessieren:

Moderne Hosting Services mit Cloud Server, Managed Server und skalierbarem Cloud Hosting für professionelle IT-Infrastrukturen

Linux PATH-Variable anzeigen und dauerhaft ändern

Linux Basics, Tutorial
VijonaGestern um 15:45 Uhr Linux PATH-Umgebungsvariable: Anzeigen, ändern und dauerhaft konfigurieren Die Linux-Umgebungsvariable PATH enthält eine durch Doppelpunkte getrennte Reihenfolge von Verzeichnissen, die von der Shell durchsucht werden, sobald ein Befehl…
Moderne Hosting Services mit Cloud Server, Managed Server und skalierbarem Cloud Hosting für professionelle IT-Infrastrukturen

OpenSSL: SSL-Zertifikate, private Schlüssel & CSRs

Security, Tutorial
VijonaGestern um 15:08 Uhr OpenSSL-Grundlagen: SSL-Zertifikate, private Schlüssel und Certificate Signing Requests OpenSSL ist ein Kommandozeilen-Werkzeug, das Administratoren häufig verwenden, um Schlüssel zu erzeugen, Certificate Signing Requests (CSRs) zu erstellen, mit…
Moderne Hosting Services mit Cloud Server, Managed Server und skalierbarem Cloud Hosting für professionelle IT-Infrastrukturen

SQL COMMIT, ROLLBACK & SAVEPOINT einfach erklärt

Databases, Tutorial
VijonaGestern um 14:36 Uhr SQL COMMIT, ROLLBACK und SAVEPOINT: Transaktionen sicher steuern COMMIT und ROLLBACK sind SQL-Befehle zur Steuerung von Transaktionen. Mit COMMIT werden die innerhalb der aktuellen Transaktion vorgenommenen Änderungen…