📋 Table of Contents
- Was ist ein Python-Dekorator?
- Ihr erster Dekorateur: Schritt für Schritt
- Der functools.wraps Fix
- Dekorateure mit Argumenten
- Mehrere Dekoratoren stapeln
- Klassenbasierte Dekoratoren
- @staticmethod vs. @classmethod vs. @property
- Anwendungsfälle aus der Praxis
- Häufige Fehler, die es zu vermeiden gilt
- Häufig gestellte Fragen
- Was ist ein Python-Dekorator?
- Ihr erster Dekorateur: Schritt für Schritt
- Der functools.wraps Fix
- Dekorateure mit Argumenten
- Mehrere Dekoratoren stapeln
- Klassenbasierte Dekoratoren
- @staticmethod vs. @classmethod vs. @property
- Anwendungsfälle aus der Praxis
- Häufige Fehler, die es zu vermeiden gilt
- FAQ
Was ist ein Python-Dekorator?
A Python-Dekorateurist ein Entwurfsmuster, mit dem Sie einer vorhandenen Funktion neues Verhalten hinzufügen können – ohne den Code der Funktion zu berühren. In Python sind Funktionen erstklassige Objekte: Sie können als Argumente übergeben, Variablen zugewiesen und von anderen Funktionen zurückgegeben werden. Dekorateure nutzen diese Eigenschaft.
Der@Syntax ist nur syntaktischer Zucker. Wenn Sie schreiben:
@my_decorator
def greet(name):
return f"Hello, {name}!"
Python führt tatsächlich Folgendes aus:
greet = my_decorator(greet)
Der Dekorateur übernimmt die ursprüngliche Funktion, verpackt sie in eine neue Funktion mit zusätzlichem Verhalten und gibt sie zurück. Der Namegreetzeigt nun auf die erweiterte Version.
Ihr erster Dekorateur: Schritt für Schritt
Lassen Sie uns ein einfaches erstellenLogging-Dekorateurdas eine Nachricht vor und nach dem Aufruf einer Funktion ausgibt.
def log_calls(func):
"""Decorator that logs function entry and exit."""
def wrapper(*args, **kwargs):
print(f"→ Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"← {func.__name__} returned {result!r}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
add(3, 4)
# Output:
# → Calling add with args=(3, 4), kwargs={}
# ← add returned 7
Aufschlüsselung:
log_callserhältfunc(die ursprüngliche Funktion)- Es definiert ein Inneres
wrapperdas akzeptiert*args, **kwargs– es funktioniert also mit jeder Funktionssignatur - Der Wrapper ruft das Original auf
func, erfasst das Ergebnis und gibt es zurück log_callsgibt den Wrapper zurück – nicht das Ergebnis des Aufrufs
Der functools.wraps Fix
Es gibt einen subtilen Fehler im Dekorator oben. Nach der Dekoration,add.__name__kehrt zurück'wrapper', nicht'add'. Das geht kaputthelp(), inspectund Stapelspuren. Repariere es mitfunctools.wraps:
import functools
def log_calls(func):
@functools.wraps(func) # ← copies __name__, __doc__, __module__, etc.
def wrapper(*args, **kwargs):
print(f"→ Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"← {func.__name__} returned {result!r}")
return result
return wrapper
@log_calls
def add(a, b):
"""Add two numbers."""
return a + b
print(add.__name__) # 'add' ✅
print(add.__doc__) # 'Add two numbers.' ✅
@functools.wraps(func)in jedem Dekorateur, den du schreibst. Das Überspringen führt zu mysteriösen Fehlern in der Protokollierung, den Test-Frameworks und den API-Dokumentationstools.Dekorateure mit Argumenten
Was ist, wenn Sie Ihren Dekorateur konfigurieren möchten? Zum Beispiel,@retry(times=3). Sie benötigen eine zusätzliche Verschachtelungsebene – aDekorateurfabrik:
import functools, time
def retry(times=3, delay=1.0, exceptions=(Exception,)):
"""Retry a function up to `times` times on specified exceptions."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_err = None
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
last_err = e
print(f" Attempt {attempt}/{times} failed: {e}")
if attempt < times:
time.sleep(delay)
raise last_err
return wrapper
return decorator
@retry(times=3, delay=0.5, exceptions=(ConnectionError,))
def fetch_data(url):
# simulate a flaky network call
raise ConnectionError("timeout")
try:
fetch_data("https://api.example.com/data")
except ConnectionError:
print("All retries exhausted.")
Die dreistufige Struktur:retry() → decorator() → wrapper(). Nennen Sie es:retry(times=3)kehrt zurückdecorator, was dauertfuncund kehrt zurückwrapper.
Mehrere Dekoratoren stapeln
Sie können mehrere Dekoratoren auf eine einzelne Funktion anwenden. Sie geltenvon unten nach oben(das Innerste zuerst):
import functools, 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__} took {elapsed:.4f}s")
return result
return wrapper
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@timer # applied second (outer)
@log_calls # applied first (inner)
def process(n):
return sum(range(n))
process(1_000_000)
# Output:
# Calling process
# process took 0.0312s
Entspricht:process = timer(log_calls(process)). Ordnung ist wichtig – äußere Dekorateure wickeln innere ein.
Klassenbasierte Dekoratoren
Jedes aufrufbare Objekt kann ein Dekorator sein. Durch die Verwendung einer Klasse erhalten Sie einen Status zwischen Aufrufen:
import functools
class CallCounter:
"""Counts how many times a function is called."""
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} called {self.count} time(s)")
return self.func(*args, **kwargs)
@CallCounter
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice") # called 1 time(s)
say_hello("Bob") # called 2 time(s)
print(say_hello.count) # 2
@staticmethod vs. @classmethod vs. @property
Python wird mit mehreren integrierten Dekoratoren ausgeliefert, die Sie ständig verwenden werden:
class Temperature:
_kelvin_offset = 273.15
def __init__(self, celsius):
self._celsius = celsius
@property
def fahrenheit(self):
"""Computed attribute — no () needed when accessing."""
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
@classmethod
def from_kelvin(cls, kelvin):
"""Factory method — creates instance from Kelvin."""
return cls(kelvin - cls._kelvin_offset)
@staticmethod
def is_valid_celsius(value):
"""Utility — no access to instance or class."""
return value >= -273.15
t = Temperature(100)
print(t.fahrenheit) # 212.0 (property)
t.fahrenheit = 32 # setter
t2 = Temperature.from_kelvin(373.15) # classmethod
print(Temperature.is_valid_celsius(-300)) # False (staticmethod)
| Dekorateur | Erstes Argument | Zugang | Anwendungsfall |
|---|---|---|---|
@property |
self |
Instanz + Klasse | Berechnete Attribute, Getter/Setter |
@classmethod |
cls |
Nur Klasse | Factory-Methoden, alternative Konstruktoren |
@staticmethod |
Keiner | Weder | Dienstprogrammfunktionen mit Namensraum für die Klasse |
Anwendungsfälle aus der Praxis
Dekoratoren unterstützen die meisten Python-Frameworks. Hier sind Muster in Produktionsqualität:
1. Caching mit LRU-Cache
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # Instant — cached results
print(fibonacci.cache_info()) # CacheInfo(hits=48, misses=51, ...)
2. Authentifizierungsschutz (Flask/FastAPI-Stil)
import functools
def require_auth(func):
"""Decorator for protected routes."""
@functools.wraps(func)
def wrapper(request, *args, **kwargs):
token = request.headers.get("Authorization", "")
if not token.startswith("Bearer "):
return {"error": "Unauthorized"}, 401
return func(request, *args, **kwargs)
return wrapper
@require_auth
def get_user_profile(request):
return {"user": "Alice", "email": "alice@example.com"}
3. Ratenbegrenzer
import functools, time
from collections import defaultdict, deque
def rate_limit(calls=10, period=60):
"""Allow at most `calls` per `period` seconds per caller."""
history = defaultdict(deque)
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
key = args[0] if args else "default" # use first arg as caller ID
q = history[key]
while q and now - q[0] > period:
q.popleft()
if len(q) >= calls:
raise RuntimeError(f"Rate limit exceeded: {calls} calls per {period}s")
q.append(now)
return func(*args, **kwargs)
return wrapper
return decorator
@rate_limit(calls=5, period=60)
def send_email(user_id, message):
print(f"Email sent to {user_id}: {message}")
Häufige Fehler, die es zu vermeiden gilt
- Vergessen
@functools.wraps– Pausenhelp(), Tracebacks und Testtools - Aufrufen der Funktion im Dekoratorkörper —
return wrapper()anstattreturn wrapper - Wird nicht verwendet
*args, **kwargs– unterbricht jede Funktion mit Parametern - Veränderbarer Standardstatus, der von allen Aufrufen gemeinsam genutzt wird– Verwenden Sie eine Abschlussvariable, keinen veränderbaren Standardwert auf Modulebene
- Fehlen
return resultim Umschlag– verschluckt stillschweigend den Rückgabewert der Funktion
# ❌ Wrong — returns None
def broken(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs) # no return!
return wrapper
# ✅ Correct
def fixed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs) # always return
return wrapper
🚀 Verbessern Sie Ihre Python-Kenntnisse
Nachdem Sie nun Dekoratoren verstanden haben, erkunden Sie, wie sie Frameworks unterstützenStrukturierung von Python-Projektenund fortgeschrittene Muster. Schauen Sie sich unseren Leitfaden anPython-Kurzausdrückefür weitere Pythonic-Code-Tricks.
Häufig gestellte Fragen
Was ist ein Python-Dekorator?
Ein Python-Dekorator ist eine Funktion, die eine andere Funktion als Eingabe verwendet und eine erweiterte Version zurückgibt – ohne das Original zu ändern. Der@Syntax ist eine Abkürzung fürfunc = decorator(func).
Wann sollte ich Dekorateure einsetzen?
Verwenden Sie Dekoratoren für übergreifende Anliegen: Protokollierung, Caching, Authentifizierung, Ratenbegrenzung, Timing, Eingabevalidierung – jedes Verhalten, das Sie auf mehrere Funktionen anwenden möchten, ohne Code zu wiederholen.
Was macht functools.wraps?
Es kopiert die Originalfunktionen__name__, __doc__und andere Attribute für den Wrapper, wobei die Identität für Debugging-, help()- und Testtools erhalten bleibt.
Können Dekorateure argumentieren?
Ja. Fügen Sie eine äußere Factory-Funktion hinzu, die die Argumente akzeptiert und den echten Dekorator zurückgibt. Muster:@retry(times=3) → retry()gibt den Dekorateur zurück.
Was ist der Unterschied zwischen @staticmethod und @classmethod?
@staticmethoderhält kein implizites erstes Argument – es ist eine einfache Funktion in einem Klassennamensraum.@classmethoderhält die Klasse (cls) als erstes Argument, nützlich für Factory-Methoden.
Wie funktionieren gestapelte Dekoratoren?
Von unten nach oben: der Dekorateur, der dem am nächsten istdefwird zuerst angewendet.@A @B def f()gleichtf = A(B(f)).
🔗 Share this article
✍️ Leave a Comment