ProgrammierungPython-Entwickler

Was sind Python-Dekoratoren mit Argumenten, wie werden sie implementiert, wo ist ihr Einsatz gerechtfertigt und welche Nuancen sind bei der Erstellung eigener Dekoratoren mit Parametern wichtig?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort

Historie des Themas

Dekoratoren sind eines der mächtigsten Werkzeuge von Python, das es ermöglicht, das Verhalten von Funktionen oder Methoden zu modifizieren. Manchmal muss man nicht nur eine Funktion "einwickeln", sondern den Dekorator mithilfe von Parametern (Argumenten) anpassen. Solche Fälle treten beim Logging, Zeitüberprüfungen, Zugriffsbeschränkungen usw. auf.

Problem

Einfache Dekoratoren akzeptieren nur eine Funktion, die verpackt werden soll. Wenn man dem Dekorator Parameter übergeben möchte, wird die Syntax komplizierter, was oft zu Fehlern führt, insbesondere bei verschachtelten Funktionen und beim Durchreichen von *args/**kwargs.

Lösung

Ein Dekorator mit Parametern wird durch eine Funktion höherer Ordnung realisiert: Zuerst wird die äußere "dekoriende" Funktion mit Argumenten aufgerufen, die den Dekorator erstellt und zurückgibt, und der Dekorator selbst erhält dann die Funktion und gibt das Wrapper zurück:

def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def greet(name): print(f"Hallo, {name}!") greet("Python") # Ausgabe: Hallo, Python! (3 Mal)

Wichtige Merkmale:

  • Ein Dekorator mit Parametern wird immer durch dreifache Verschachtelung von Funktionen realisiert
  • Das Wrapper muss das Ergebnis zurückgeben und korrekt *args/**kwargs durchreichen
  • Vergessen Sie nicht functools.wraps, um Metadaten zu erhalten

Trickfragen.

Warum kann ein Dekorator mit Parametern nicht auf die gleiche Weise implementiert werden wie ein einfacher Dekorator?

Wenn Sie @decorator anwenden, übergibt Python die Funktion als Argument an den Dekorator. Wenn Sie Klammern hinzufügen (@decorator()), ruft Python zuerst die Funktion auf und nur das Ergebnis wird als Dekorator interpretiert.

def deco(func): # einfacher Dekorator: @deco def deco_with_args(arg): # Dekorator mit Argument: @deco_with_args(arg)

Wie unterscheiden sich Dekoratoren mit Argumenten grundsätzlich von Dekoratoren ohne Argumente auf der Aufrufebene?

Dekoratoren ohne Argumente erhalten die Funktion als Eingabe, während Dekoratoren mit Argumenten nicht die Funktion, sondern Parameter erhalten und einen Dekorator zurückgeben.

Wie verwendet man functools.wraps richtig und warum ist das wichtig?

functools.wraps(func) im Wrapper speichert den Namen, die Dokumentationszeile und andere Metadaten der ursprünglichen Funktion, andernfalls gehen all diese Informationen im Wrapper verloren, was Debugging und Introspektion erschwert.

import functools def deco_with_args(arg): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator

Typische Fehler und Anti-Patterns

  • Vergessen, dass drei verschachtelte Funktionen notwendig sind, und machen nur zwei (oder überhaupt nur eine), das Ergebnis wird kein Dekorator sein
  • *args/**kwargs werden nicht innerhalb des Wrappers durchgereicht
  • Verlust von Metainformationen der Funktion aufgrund des Fehlens von functools.wraps

Beispiel aus dem Leben

Negativer Fall

Ein Dekorator wurde ohne Berücksichtigung der Parameter oder mit falscher Anzahl an verschachtelten Funktionen implementiert:

def log(level): def wrapper(func): # Fehler — Wrapper muss tiefer sein print(f"Log: {level}") func() # Funktion wird nicht als Dekorator zurückgegeben return wrapper @log("INFO") def action(): print("Arbeit!")

Vorteile:

  • Sieht einfach aus

Nachteile:

  • Der Dekorator funktioniert nicht, die Funktion wird beim Dekorieren aufgerufen und nicht beim Aufruf von action()

Positiver Fall

Verwendung von functools.wraps und korrekten verschachtelten Funktionen:

import functools def timer(units): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): import time start = time.time() result = func(*args, **kwargs) end = time.time() if units == 'ms': duration = (end - start) * 1000 else: duration = end - start print(f"Dauer: {duration:.4f} {units}") return result return wrapper return decorator @timer('ms') def op(): sum(range(1000)) op()

Vorteile:

  • Korrekte Struktur, leicht erweiterbar, sauberes Logging

Nachteile:

  • Schwieriger zu lesen, besonders für Anfänger