Einführung
Python-Dekorateure (Decorators) sind ein mächtiges Werkzeug, das es Ihnen ermöglicht, das Verhalten von Funktionen zu ändern, ohne ihren Quellcode zu verändern. In diesem Tutorial werden wir untersuchen, wie Sie mehrere Dekorateure auf eine einzelne Python-Funktion anwenden können, wodurch eine Welt voller Möglichkeiten für Code-Wiederverwendung und Funktionsverbesserung eröffnet wird.
Python-Dekorateure verstehen
Was sind Dekorateure in Python?
Dekorateure (Decorators) in Python sind eine mächtige und flexible Möglichkeit, das Verhalten einer Funktion oder einer Klasse zu ändern, ohne ihren Quellcode zu verändern. Sie ermöglichen es, eine Funktion mit einer anderen Funktion zu "umhüllen", sodass die umhüllende Funktion Code vor und/oder nach dem Aufruf der ursprünglichen Funktion ausführen kann.
Warum sollten Sie Dekorateure verwenden?
Dekorateure sind für verschiedene Aufgaben nützlich, wie beispielsweise:
- Protokollieren von Funktionsaufrufen
- Zwischenspeichern von Funktionsergebnissen
- Durchsetzung von Zugangskontrollen
- Messen der Ausführungszeit von Funktionen
- Wiederholen fehlgeschlagener Funktionsaufrufe
Wie funktionieren Dekorateure?
Dekorateure in Python werden mit dem @-Symbol definiert, gefolgt von der Dekoratorfunktion. Die Dekoratorfunktion nimmt eine Funktion als Argument, führt einige zusätzliche Verarbeitungen durch und gibt dann eine neue Funktion zurück, die anstelle der ursprünglichen Funktion aufgerufen werden kann.
Hier ist ein einfaches Beispiel für eine Dekoratorfunktion, die die an eine Funktion übergebenen Argumente protokolliert:
def log_args(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args} and kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
@log_args
def add_numbers(a, b):
return a + b
result = add_numbers(2, 3)
print(result)
Dies wird folgendes ausgeben:
Calling add_numbers with args=(2, 3) and kwargs={}
5
Verschachtelte Dekorateure
Dekorateure können verschachtelt werden, sodass Sie mehrere Dekorateure auf eine einzelne Funktion anwenden können. Die Reihenfolge, in der die Dekorateure angewendet werden, ist wichtig, da sie die Reihenfolge bestimmt, in der die Dekoratorfunktionen ausgeführt werden.
def uppercase(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
def reverse(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result[::-1]
return wrapper
@uppercase
@reverse
def greet(name):
return f"Hello, {name}!"
print(greet("LabEx"))
Dies wird folgendes ausgeben:
!XEbal,OLLEH
Dekorator-Argumente
Dekorateure können auch Argumente akzeptieren, wodurch Sie das Verhalten des Dekorators anpassen können. Dies ist nützlich, wenn Sie einen Dekorator erstellen möchten, der auf verschiedene Weise konfiguriert werden kann.
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
result = ""
for _ in range(n):
result += func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
return f"Hello, {name}!"
print(greet("LabEx"))
Dies wird folgendes ausgeben:
Hello, LabEx!Hello, LabEx!Hello, LabEx!
Anwenden mehrerer Dekorateure
Die Reihenfolge der Dekoratoranwendung
Wenn Sie mehrere Dekorateure auf eine Funktion anwenden, ist die Reihenfolge, in der sie angewendet werden, wichtig. Die Dekorateure werden von unten nach oben angewendet, was bedeutet, dass der innerste Dekorator zuerst und der äußerste Dekorator zuletzt angewendet wird.
def uppercase(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
def reverse(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result[::-1]
return wrapper
@uppercase
@reverse
def greet(name):
return f"Hello, {name}!"
print(greet("LabEx"))
Dies wird folgendes ausgeben:
!XEBAL,OLLEH
Stapeln von Dekorateuren
Sie können auch mehrere Dekorateure auf eine einzelne Funktion stapeln, indem Sie sie nacheinander anwenden. Dies ist äquivalent zu verschachtelten Dekorateuren, kann aber den Code lesbarer machen.
def uppercase(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
def reverse(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result[::-1]
return wrapper
def greet(name):
return f"Hello, {name}!"
greet_upper_reverse = uppercase(reverse(greet))
print(greet_upper_reverse("LabEx"))
Dies wird folgendes ausgeben:
!XEBAL,OLLEH
Dekorieren von Methoden
Dekorateure können auch verwendet werden, um das Verhalten von Methoden in einer Klasse zu ändern. Die gleichen Prinzipien gelten, aber die Dekoratorfunktion muss den self-Parameter als erstes Argument nehmen.
def log_method(func):
def wrapper(self, *args, **kwargs):
print(f"Calling {func.__name__} on {self.__class__.__name__} with args={args} and kwargs={kwargs}")
return func(self, *args, **kwargs)
return wrapper
class Person:
def __init__(self, name):
self.name = name
@log_method
def greet(self, message):
return f"{message}, {self.name}!"
person = Person("LabEx")
print(person.greet("Hello"))
Dies wird folgendes ausgeben:
Calling greet on Person with args=('Hello',) and kwargs={}
Hello, LabEx!
Dekorieren von Klassen
Dekorateure können auch verwendet werden, um das Verhalten ganzer Klassen zu ändern. In diesem Fall nimmt die Dekoratorfunktion eine Klasse als Argument und gibt eine neue Klasse mit dem gewünschten Verhalten zurück.
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class MyClass:
def __init__(self, value):
self.value = value
obj1 = MyClass(42)
obj2 = MyClass(24)
print(obj1 is obj2) ## True
print(obj1.value) ## 42
print(obj2.value) ## 42
In diesem Beispiel stellt der singleton-Dekorator sicher, dass nur eine Instanz der MyClass-Klasse erstellt wird, unabhängig davon, wie oft die Klasse instanziiert wird.
Praktische Beispiele für Dekorateure
Protokollierungs-Dekorator
Ein häufiger Anwendungsfall für Dekorateure ist die Protokollierung von Funktionsaufrufen. Dies kann für das Debugging, die Überwachung oder die Prüfung nützlich sein.
def log_function_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args} and kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
@log_function_call
def add_numbers(a, b):
return a + b
result = add_numbers(2, 3)
print(result)
Dies wird folgendes ausgeben:
Calling add_numbers with args=(2, 3) and kwargs={}
5
Caching-Dekorator
Dekorateure können auch verwendet werden, um die Ergebnisse aufwändiger Funktionsaufrufe zu speichern (cachen), was die Leistung verbessert.
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n <= 1:
return n
else:
return (fibonacci(n-1) + fibonacci(n-2))
print(fibonacci(100))
Der lru_cache-Dekorator aus dem functools-Modul bietet eine einfache Möglichkeit, einen Least Recently Used (LRU)-Cache für Funktionsergebnisse zu implementieren.
Zugangskontroll-Dekorator
Dekorateure können verwendet werden, um die Zugangskontrolle zu Funktionen oder Methoden durchzusetzen und sicherzustellen, dass nur autorisierte Benutzer bestimmte Funktionen nutzen können.
from functools import wraps
def require_admin(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not is_admin(args[0]):
raise ValueError("Access denied. You must be an admin.")
return func(*args, **kwargs)
return wrapper
class User:
def __init__(self, name, is_admin):
self.name = name
self.is_admin = is_admin
@require_admin
def delete_user(self, user_to_delete):
print(f"Deleting user: {user_to_delete.name}")
admin = User("LabEx", True)
regular_user = User("John", False)
admin.delete_user(regular_user) ## Funktioniert
regular_user.delete_user(admin) ## Wirft ValueError
In diesem Beispiel überprüft der require_admin-Dekorator, ob der Benutzer, der die delete_user-Methode aufruft, ein Administrator ist, bevor die Operation fortgesetzt wird.
Wiederholungs-Dekorator
Dekorateure können auch verwendet werden, um einen Wiederholungsmechanismus für Funktionen zu implementieren, die möglicherweise aufgrund vorübergehender Probleme wie Netzwerkfehler oder API-Ratenlimits fehlschlagen.
import time
from functools import wraps
def retry(max_retries=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
retries = 0
while retries < max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Function {func.__name__} failed. Retrying... ({retries+1}/{max_retries})")
retries += 1
time.sleep(delay)
raise Exception(f"Maximum number of retries ({max_retries}) reached for function {func.__name__}")
return wrapper
return decorator
@retry(max_retries=3, delay=2)
def flaky_function():
## Simulate a flaky function that fails 50% of the time
if random.random() < 0.5:
raise Exception("Oops, something went wrong!")
return "Success!"
print(flaky_function())
In diesem Beispiel wird der retry-Dekorator die flaky_function automatisch bis zu 3 Mal wiederholen, mit einer Verzögerung von 2 Sekunden zwischen jedem Versuch, bevor eine Ausnahme ausgelöst wird.
Dies sind nur einige Beispiele für die vielen praktischen Anwendungsfälle von Dekorateuren in Python. Dekorateure sind ein mächtiges und flexibles Werkzeug, das Ihnen helfen kann, modulareren, wartbareren und wiederverwendbaren Code zu schreiben.
Zusammenfassung
Am Ende dieses Tutorials werden Sie ein solides Verständnis davon haben, wie Sie mehrere Dekorateure auf eine einzelne Python-Funktion anwenden können. Sie werden praktische Beispiele und reale Anwendungsfälle kennenlernen, die es Ihnen ermöglichen, modulareren, flexibleren und wartbareren Python-Code zu schreiben.



