Wie man mehrere Dekorateure auf eine einzelne Python-Funktion anwendet

PythonPythonBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

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.