Python-Dekorierer-Muster

Die Kernidee des Dekorator-Musters ist das Umhüllen — indem ein Wrapper-Objekt (Dekorator) erstellt wird, das das ursprüngliche Objekt umhüllt, wodurch seine Funktionalität erweitert wird, ohne das ursprüngliche Objekt zu verändern.

Grundlegende Konzepte

Das Dekorator-Muster besteht aus vier Hauptrollen:

  1. Komponenten-Schnittstelle (Component): Definiert die gemeinsame Schnittstelle für das dekorierte Objekt und den Dekorator
  2. Konkrete Komponente: Das ursprüngliche Objekt, das dekoriert werden muss
  3. Dekorator-Basisklasse (Decorator): Hält eine Referenz auf ein Komponentenobjekt und implementiert die Komponenten-Schnittstelle
  4. Konkreter Dekorator: Implementiert die spezifische Dekorationsfunktionalität


Grundlegende Syntax von Dekoratoren

Funktionsdekoratoren

Funktionsdekoratoren sind die gebräuchlichste Form von Dekoratoren. Sie nehmen eine Funktion als Argument entgegen und geben eine neue Funktion zurück.

Beispiel

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Einige Operationen vor der Funktionsausführung")
        result = func(*args, **kwargs)
        print("Einige Operationen nach der Funktionsausführung")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hallo, {name}!")

# Dekorator verwenden
say_hello("Alice")

Ausgabe:

函数执行前的一些操作
Hello, Alice!
函数执行后的一些操作

Dekoratoren mit Parametern

Wenn Sie Parameter an den Dekorator übergeben müssen, müssen Sie eine weitere Ebene von Funktionen verschachteln:

Beispiel

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(times):
                print(f"Ausführung {i+1}:")
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

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

greet("Bob")

Klassendekoratoren

Neben Funktionsdekoratoren unterstützt Python auch Klassendekoratoren. Klassendekoratoren funktionieren, indem sie__call__Methoden implementieren, um zu funktionieren.

Einfacher Klassendekorator

Beispiel

class TimerDecorator:
    def __init__(self, func):
        self.func = func
   
    def __call__(self, *args, **kwargs):
        import time
        start_time = time.time()
        result = self.func(*args, **kwargs)
        end_time = time.time()
        print(f"Funktion {self.func.__name__} ausgeführt in {end_time - start_time:.4f} Sekunden")
        return result

@TimerDecorator
def calculate_sum(n):
    return sum(range(n))

result = calculate_sum(1000000)
print(f"Berechnungsergebnis: {result}")

Klassen-Dekorator mit Parametern

Beispiel

class LogDecorator:
    def __init__(self, level="INFO"):
        self.level = level
   
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(f"[{self.level}] Funktion wird aufgerufen: {func.__name__}")
            print(f"[{self.level}] Argumente: args={args}, kwargs={kwargs}")
            result = func(*args, **kwargs)
            print(f"[{self.level}] Rückgabewert: {result}")
            return result
        return wrapper

@LogDecorator(level="DEBUG")
def multiply(a, b):
    return a * b

multiply(5, 3)

Eingebaute Dekoratoren

Python bietet einige nützliche eingebaute Dekoratoren:

@staticmethod und @classmethod

Beispiel

class Calculator:
    @staticmethod
    def add(x, y):
        return x + y
   
    @classmethod
    def multiply(cls, x, y):
        return x * y

# Statische Methode verwenden
result1 = Calculator.add(5, 3)
print(f"Ergebnis der statischen Methode: {result1}")

# Klassenmethode verwenden
result2 = Calculator.multiply(5, 3)
print(f"Ergebnis der Klassenmethode: {result2}")

@property

Beispiel

class Circle:
    def __init__(self, radius):
        self._radius = radius
   
    @property
    def radius(self):
        return self._radius
   
    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("Radius muss eine positive Zahl sein")
        self._radius = value
   
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

circle = Circle(5)
print(f"Radius: {circle.radius}")
print(f"Fläche: {circle.area}")

circle.radius = 10
print(f"Neuer Radius: {circle.radius}")
print(f"Neue Fläche: {circle.area}")

Praktische Anwendungsszenarien von Dekoratoren

1. Protokollierung

Beispiel

def log_execution(func):
    def wrapper(*args, **kwargs):
        print(f"Starte Ausführung: {func.__name__}")
        try:
            result = func(*args, **kwargs)
            print(f"Erfolgreich abgeschlossen: {func.__name__}")
            return result
        except Exception as e:
            print(f"Ausführung fehlgeschlagen: {func.__name__}, Fehler: {e}")
            raise
    return wrapper

@log_execution
def process_data(data):
    # Datenverarbeitung simulieren
    if not data:
        raise ValueError("Daten dürfen nicht leer sein")
    return [x * 2 for x in data]

# Test
data = [1, 2, 3]
result = process_data(data)
print(f"Verarbeitungsergebnis: {result}")

2. Berechtigungsüberprüfung

Beispiel

def require_login(func):
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated', False):
            raise PermissionError("Benutzer ist nicht angemeldet")
        return func(user, *args, **kwargs)
    return wrapper

@require_login
def view_profile(user):
    return f"Profil von Benutzer {user['username']} anzeigen"

# Test
user1 = {'username': 'alice', 'is_authenticated': True}
user2 = {'username': 'bob', 'is_authenticated': False}

print(view_profile(user1))  # Normale Ausführung
# print(view_profile(user2)) # wird einen PermissionError auslösen

3. Cache-Dekorator

Beispiel

def cache_results(func):
    cache = {}
   
    def wrapper(*args):
        if args in cache:
            print(f"Ergebnis aus dem Cache holen: {args}")
            return cache[args]
        result = func(*args)
        cache[args] = result
        print(f"Ergebnis berechnen und zwischenspeichern: {args} -> {result}")
        return result
    return wrapper

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

# Cache-Effekt testen
print(fibonacci(5))
print(fibonacci(5))  # Dieses Mal wird es aus dem Cache abgerufen

Ausführungsreihenfolge mehrerer Dekoratoren

Bei Verwendung mehrerer Dekoratoren verläuft ihre Ausführungsreihenfolge von unten nach oben:

Beispiel

def decorator1(func):
    def wrapper():
        print("Dekorator 1 - davor")
        func()
        print("Dekorator 1 - danach")
    return wrapper

def decorator2(func):
    def wrapper():
        print("Dekorator 2 - davor")
        func()
        print("Dekorator 2 - danach")
    return wrapper

@decorator1
@decorator2
def my_function():
    print("Ursprüngliche Funktion")

my_function()

Ausgabe:

装饰器1 - 前
装饰器2 - 前
原始函数
装饰器2 - 后
装饰器1 - 后

Beibehalten der Funktionsmetadaten

Wenn Sie Dekoratoren verwenden, werden die Metadaten der ursprünglichen Funktion (wie Funktionsname, Docstring usw.) durch die Wrapper-Funktion überschrieben. Sie könnenfunctools.wrapsum diese Informationen beizubehalten:

Beispiel

from functools import wraps

def preserve_metadata(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Docstring der Wrapper-Funktion"""
        return func(*args, **kwargs)
    return wrapper

@preserve_metadata
def example_function():
    """Dies ist der Docstring der ursprünglichen Funktion"""
    pass

print(f"Funktionsname: {example_function.__name__}")
print(f"Dokumentationsstring: {example_function.__doc__}")

Praktische Übungen

Übung 1: Erstellen eines Dekorators zur Leistungsüberwachung

Schreibe einen Dekorator, um die Ausführungszeit und den Speicherverbrauch einer Funktion zu überwachen.

Beispiel

import time
import tracemalloc

def performance_monitor(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # Speicherüberwachung starten
        tracemalloc.start()
       
        # Startzeit erfassen
        start_time = time.time()
       
        # Funktion ausführen
        result = func(*args, **kwargs)
       
        # Endzeit erfassen
        end_time = time.time()
       
        # Speicherverbrauch abrufen
        current, peak = tracemalloc.get_traced_memory()
        tracemalloc.stop()
       
        print(f"Leistungsbericht für Funktion {func.__name__}:")
        print(f"Ausführungszeit: {end_time - start_time:.4f} Sekunden")
        print(f"Speicherverbrauch: aktuell {current/1024:.2f} KB, Spitzenwert {peak/1024:.2f} KB")
       
        return result
    return wrapper

@performance_monitor
def process_large_data():
    """Simuliert die Verarbeitung großer Datenmengen"""
    data = [i**2 for i in range(100000)]
    return sum(data)

process_large_data()

Übung 2: Implementierung eines Dekorators für Wiederholungsmechanismen

Erstelle einen Dekorator, der bei fehlgeschlagener Funktionsausführung automatisch eine angegebene Anzahl von Wiederholungen durchführt.

Beispiel

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts == max_attempts:
                        print(f"Funktion {func.__name__} fehlgeschlagen, maximale Anzahl an Wiederholungsversuchen erreicht")
                        raise
                    print(f"Funktion {func.__name__} fehlgeschlagen bei Versuch {attempts}: {e}")
                    print(f"Warte {delay} Sekunden vor dem erneuten Versuch...")
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_operation():
    """Simuliert eine instabile Operation"""
    import random
    if random.random() < 0.7:  # 70% Wahrscheinlichkeit eines Fehlers
        raise ValueError("Zufälliger Fehler")
    return "Operation erfolgreich"

# Wiederholungsmechanismus testen
result = unstable_operation()
print(f"Endergebnis: {result}")

Zusammenfassung

Python-Dekoratoren sind ein mächtiges und flexibles Werkzeug, das die folgenden Vorteile bietet:

  1. Code-Wiederverwendung: Trennt querschnittliche Belange (wie Logging, Caching, Validierung) von der Geschäftslogik
  2. Dynamische Erweiterung: Neue Funktionalität hinzufügen, ohne den ursprünglichen Code zu ändern
  3. Prägnant halten: Verwenden Sie@Syntax macht Code klarer und lesbarer
  4. Folgt dem Open-Closed-Prinzip: Offen für Erweiterung, geschlossen für Modifikation

Best Practices für die Verwendung von Decorators:

  • Nutzungfunctools.wrapsFunktionsmetadaten beibehalten
  • Die alleinige Verantwortlichkeit des Decorators beibehalten
  • Ausnahmen in Decorators ordnungsgemäß behandeln
  • Auf die Ausführungsreihenfolge von Decorators achten
Weitere Erweiterungen