Decorator Verkettung und parametrisierte Decorators

PythonPythonBeginner
Jetzt üben

This tutorial is from open-source community. Access the source code

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

Einführung

In diesem Lab lernen Sie etwas über Decorators (Dekorateure) in Python kennen, eine leistungsstarke Funktion, die das Verhalten von Funktionen und Methoden ändern kann. Decorators werden üblicherweise für Aufgaben wie Logging (Protokollierung), Leistungsmessung, Zugriffskontrolle und Typüberprüfung verwendet.

Sie werden lernen, wie Sie mehrere Decorators verketten, Decorators erstellen, die Parameter akzeptieren, die Metadaten von Funktionen beim Verwenden von Decorators beibehalten und Decorators auf verschiedene Arten von Klassenmethoden anwenden. Die Dateien, mit denen Sie arbeiten werden, sind logcall.py, validate.py und sample.py.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/BasicConceptsGroup -.-> python/type_conversion("Type Conversion") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/lambda_functions("Lambda Functions") python/FunctionsGroup -.-> python/scope("Scope") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/class_static_methods("Class Methods and Static Methods") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/type_conversion -.-> lab-132515{{"Decorator Verkettung und parametrisierte Decorators"}} python/function_definition -.-> lab-132515{{"Decorator Verkettung und parametrisierte Decorators"}} python/lambda_functions -.-> lab-132515{{"Decorator Verkettung und parametrisierte Decorators"}} python/scope -.-> lab-132515{{"Decorator Verkettung und parametrisierte Decorators"}} python/classes_objects -.-> lab-132515{{"Decorator Verkettung und parametrisierte Decorators"}} python/class_static_methods -.-> lab-132515{{"Decorator Verkettung und parametrisierte Decorators"}} python/decorators -.-> lab-132515{{"Decorator Verkettung und parametrisierte Decorators"}} end

Beibehalten von Funktionsmetadaten in Decorators

In Python sind Decorators (Dekorateure) ein leistungsstarkes Werkzeug, das es Ihnen ermöglicht, das Verhalten von Funktionen zu ändern. Allerdings gibt es ein kleines Problem, wenn Sie einen Decorator verwenden, um eine Funktion zu umhüllen. Standardmäßig gehen die Metadaten der ursprünglichen Funktion, wie ihr Name, ihre Dokumentationszeichenfolge (Docstring) und ihre Anmerkungen (Annotations), verloren. Metadaten sind wichtig, da sie die Introspektion (die Untersuchung der Code-Struktur) und die Generierung von Dokumentation erleichtern. Lassen Sie uns zunächst dieses Problem verifizieren.

Öffnen Sie Ihr Terminal in der WebIDE. Wir werden einige Python-Befehle ausführen, um zu sehen, was passiert, wenn wir einen Decorator verwenden. Die folgenden Befehle erstellen eine einfache Funktion add, die mit einem Decorator umhüllt wird, und geben dann die Funktion und ihren Docstring aus.

cd ~/project
python3 -c "from logcall import logged; @logged
def add(x,y):
    'Adds two things'
    return x+y
    
print(add)
print(add.__doc__)"

Wenn Sie diese Befehle ausführen, sehen Sie eine Ausgabe ähnlich dieser:

<function wrapper at 0x...>
None

Beachten Sie, dass anstelle des Funktionsnamens add der Name wrapper angezeigt wird. Und der Docstring, der 'Adds two things' sein sollte, ist None. Dies kann ein großes Problem sein, wenn Sie Tools verwenden, die auf diesen Metadaten basieren, wie Introspektions-Tools oder Dokumentations-Generatoren.

Beheben des Problems mit functools.wraps

Python's functools-Modul kommt uns hier zu Hilfe. Es bietet einen wraps-Decorator, der uns dabei helfen kann, die Funktionsmetadaten zu bewahren. Lassen Sie uns sehen, wie wir unseren logged-Decorator so ändern können, dass er wraps verwendet.

  1. Öffnen Sie zunächst die Datei logcall.py in der WebIDE. Sie können in das Projektverzeichnis navigieren, indem Sie den folgenden Befehl im Terminal ausführen:
cd ~/project
  1. Aktualisieren Sie nun den logged-Decorator in logcall.py mit dem folgenden Code. Der @wraps(func)-Decorator ist hier der Schlüssel. Er kopiert alle Metadaten von der ursprünglichen Funktion func auf die Wrapper-Funktion.
from functools import wraps

def logged(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper
  1. Der @wraps(func)-Decorator erledigt eine wichtige Aufgabe. Er nimmt alle Metadaten (wie den Namen, den Docstring und die Anmerkungen) von der ursprünglichen Funktion func und fügt sie der wrapper-Funktion hinzu. Auf diese Weise hat die dekorierte Funktion die richtigen Metadaten.

  2. Lassen Sie uns unseren verbesserten Decorator testen. Führen Sie die folgenden Befehle im Terminal aus:

python3 -c "from logcall import logged; @logged
def add(x,y):
    'Adds two things'
    return x+y
    
print(add)
print(add.__doc__)"

Jetzt sollten Sie sehen:

<function add at 0x...>
Adds two things

Toll! Der Funktionsname und der Docstring werden beibehalten. Dies bedeutet, dass unser Decorator jetzt wie erwartet funktioniert und die Metadaten der ursprünglichen Funktion intakt sind.

Beheben des Problems im validate.py-Decorator

Lassen Sie uns nun dasselbe Verfahren auf den validated-Decorator in validate.py anwenden. Dieser Decorator wird verwendet, um die Typen der Funktionsargumente und den Rückgabewert basierend auf den Anmerkungen der Funktion zu überprüfen.

  1. Öffnen Sie validate.py in der WebIDE.

  2. Aktualisieren Sie den validated-Decorator mit dem @wraps-Decorator. Der folgende Code zeigt, wie dies geht. Der @wraps(func)-Decorator wird der wrapper-Funktion innerhalb des validated-Decorators hinzugefügt, um die Metadaten zu bewahren.

from functools import wraps

class Integer:
    @classmethod
    def __instancecheck__(cls, x):
        return isinstance(x, int)

def validated(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ## Get function annotations
        annotations = func.__annotations__
        ## Check arguments against annotations
        for arg_name, arg_value in zip(func.__code__.co_varnames, args):
            if arg_name in annotations and not isinstance(arg_value, annotations[arg_name]):
                raise TypeError(f'Expected {arg_name} to be {annotations[arg_name].__name__}')

        ## Run the function and get the result
        result = func(*args, **kwargs)

        ## Check the return value
        if 'return' in annotations and not isinstance(result, annotations['return']):
            raise TypeError(f'Expected return value to be {annotations["return"].__name__}')

        return result
    return wrapper
  1. Lassen Sie uns testen, ob unser validated-Decorator jetzt die Metadaten beibehält. Führen Sie die folgenden Befehle im Terminal aus:
python3 -c "from validate import validated, Integer; @validated
def multiply(x: Integer, y: Integer) -> Integer:
    'Multiplies two integers'
    return x * y
    
print(multiply)
print(multiply.__doc__)"

Sie sollten sehen:

<function multiply at 0......>
Multiplies two integers

Jetzt behalten beide Decorators, logged und validated, die Metadaten der Funktionen, die sie dekorieren, ordnungsgemäß bei. Dies stellt sicher, dass die Funktionen, wenn Sie diese Decorators verwenden, immer noch ihre ursprünglichen Namen, Docstrings und Anmerkungen haben, was für die Lesbarkeit und Wartbarkeit des Codes sehr nützlich ist.

✨ Lösung prüfen und üben

Erstellen von Decorators mit Argumenten

Bisher haben wir den @logged-Decorator verwendet, der immer eine feste Nachricht ausgibt. Aber was, wenn Sie das Nachrichtenformat anpassen möchten? In diesem Abschnitt lernen wir, wie man einen neuen Decorator erstellt, der Argumente akzeptieren kann. Dies gibt Ihnen mehr Flexibilität bei der Verwendung von Decorators.

Verständnis von parametrisierten Decorators

Ein parametrisierter Decorator ist eine besondere Art von Funktion. Anstatt direkt eine andere Funktion zu modifizieren, gibt er einen Decorator zurück. Die allgemeine Struktur eines parametrisierten Decorators sieht wie folgt aus:

def decorator_with_args(arg1, arg2, ...):
    def actual_decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            ## Use arg1, arg2, ... here
            ## Call the original function
            return func(*args, **kwargs)
        return wrapper
    return actual_decorator

Wenn Sie @decorator_with_args(value1, value2) in Ihrem Code verwenden, ruft Python zunächst decorator_with_args(value1, value2) auf. Dieser Aufruf gibt den eigentlichen Decorator zurück, der dann auf die Funktion angewendet wird, die auf die @-Syntax folgt. Dieser zweistufige Prozess ist der Schlüssel für die Funktionsweise von parametrisierten Decorators.

Erstellen des logformat-Decorators

Lassen Sie uns einen @logformat(fmt)-Decorator erstellen, der eine Formatzeichenfolge als Argument nimmt. Dies ermöglicht es uns, die Protokollnachricht anzupassen.

  1. Öffnen Sie logcall.py in der WebIDE und fügen Sie den neuen Decorator hinzu. Der folgende Code zeigt, wie man sowohl den bestehenden logged-Decorator als auch den neuen logformat-Decorator definiert:
from functools import wraps

def logged(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

def logformat(fmt):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(fmt.format(func=func))
            return func(*args, **kwargs)
        return wrapper
    return decorator

Im logformat-Decorator nimmt die äußere Funktion logformat eine Formatzeichenfolge fmt als Argument. Sie gibt dann die decorator-Funktion zurück, die der eigentliche Decorator ist, der die Ziel-Funktion modifiziert.

  1. Jetzt testen wir unseren neuen Decorator, indem wir sample.py ändern. Der folgende Code zeigt, wie man sowohl den logged- als auch den logformat-Decorator auf verschiedene Funktionen anwendet:
from logcall import logged, logformat

@logged
def add(x, y):
    "Adds two numbers"
    return x + y

@logged
def sub(x, y):
    "Subtracts y from x"
    return x - y

@logformat('{func.__code__.co_filename}:{func.__name__}')
def mul(x, y):
    "Multiplies two numbers"
    return x * y

Hier verwenden die Funktionen add und sub den logged-Decorator, während die Funktion mul den logformat-Decorator mit einer benutzerdefinierten Formatzeichenfolge verwendet.

  1. Führen Sie die aktualisierte Datei sample.py aus, um die Ergebnisse zu sehen. Öffnen Sie Ihr Terminal und führen Sie den folgenden Befehl aus:
cd ~/project
python3 -c "import sample; print(sample.add(2, 3)); print(sample.mul(2, 3))"

Sie sollten eine Ausgabe ähnlich der folgenden sehen:

Calling add
5
sample.py:mul
6

Diese Ausgabe zeigt, dass der logged-Decorator wie erwartet den Funktionsnamen ausgibt und der logformat-Decorator die benutzerdefinierte Formatzeichenfolge verwendet, um den Dateinamen und den Funktionsnamen auszugeben.

Neudefinieren des logged-Decorators unter Verwendung von logformat

Jetzt, da wir einen flexibleren logformat-Decorator haben, können wir unseren ursprünglichen logged-Decorator unter Verwendung dieses neuen Decorators neu definieren. Dies hilft uns, Code wiederzuverwenden und ein konsistentes Protokollformat beizubehalten.

  1. Aktualisieren Sie logcall.py mit dem folgenden Code:
from functools import wraps

def logformat(fmt):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(fmt.format(func=func))
            return func(*args, **kwargs)
        return wrapper
    return decorator

## Define logged using logformat
logged = lambda func: logformat("Calling {func.__name__}")(func)

Hier verwenden wir eine Lambda-Funktion, um den logged-Decorator in Bezug auf den logformat-Decorator zu definieren. Die Lambda-Funktion nimmt eine Funktion func und wendet den logformat-Decorator mit einer bestimmten Formatzeichenfolge an.

  1. Testen Sie, ob der neu definierte logged-Decorator noch funktioniert. Öffnen Sie Ihr Terminal und führen Sie den folgenden Befehl aus:
cd ~/project
python3 -c "from logcall import logged; @logged
def greet(name):
    return f'Hello, {name}'
    
print(greet('World'))"

Sie sollten sehen:

Calling greet
Hello, World

Dies zeigt, dass der neu definierte logged-Decorator wie erwartet funktioniert und wir den logformat-Decorator erfolgreich wiederverwendet haben, um ein konsistentes Protokollformat zu erreichen.

✨ Lösung prüfen und üben

Anwenden von Decorators auf Klassenmethoden

Jetzt werden wir untersuchen, wie Decorators mit Klassenmethoden interagieren. Dies kann etwas tricky sein, da Python verschiedene Arten von Methoden hat: Instanzmethoden, Klassenmethoden, statische Methoden und Properties. Decorators sind Funktionen, die eine andere Funktion als Argument nehmen und das Verhalten dieser Funktion erweitern, ohne sie explizit zu modifizieren. Wenn wir Decorators auf Klassenmethoden anwenden, müssen wir darauf achten, wie sie mit diesen verschiedenen Methodentypen zusammenarbeiten.

Das Problem verstehen

Lassen Sie uns sehen, was passiert, wenn wir unseren @logged-Decorator auf verschiedene Methodentypen anwenden. Der @logged-Decorator wird wahrscheinlich verwendet, um Informationen über Methodenaufrufe zu protokollieren.

  1. Erstellen Sie eine neue Datei methods.py in der WebIDE. Diese Datei wird unsere Klasse mit verschiedenen Methodentypen enthalten, die mit dem @logged-Decorator dekoriert sind.
from logcall import logged

class Spam:
    @logged
    def instance_method(self):
        print("Instance method called")
        return "instance result"

    @logged
    @classmethod
    def class_method(cls):
        print("Class method called")
        return "class result"

    @logged
    @staticmethod
    def static_method():
        print("Static method called")
        return "static result"

    @logged
    @property
    def property_method(self):
        print("Property method called")
        return "property result"

In diesem Code haben wir eine Klasse Spam mit vier verschiedenen Methodentypen. Jede Methode ist mit dem @logged-Decorator dekoriert, und einige sind auch mit anderen eingebauten Decorators wie @classmethod, @staticmethod und @property dekoriert.

  1. Lassen Sie uns testen, wie es funktioniert. Wir werden einen Python-Befehl im Terminal ausführen, um diese Methoden aufzurufen und die Ausgabe zu sehen.
cd ~/project
python3 -c "from methods import Spam; s = Spam(); print(s.instance_method()); print(Spam.class_method()); print(Spam.static_method()); print(s.property_method)"

Wenn Sie diesen Befehl ausführen, werden Sie möglicherweise einige Probleme bemerken:

  • Der @property-Decorator funktioniert möglicherweise nicht richtig mit unserem @logged-Decorator. Der @property-Decorator wird verwendet, um eine Methode als Property zu definieren, und er hat eine spezifische Arbeitsweise. Wenn er mit dem @logged-Decorator kombiniert wird, können Konflikte auftreten.
  • Die Reihenfolge der Decorators spielt für @classmethod und @staticmethod eine Rolle. Die Reihenfolge, in der Decorators angewendet werden, kann das Verhalten der Methode ändern.

Die Reihenfolge der Decorators

Wenn Sie mehrere Decorators anwenden, werden sie von unten nach oben angewendet. Dies bedeutet, dass der Decorator, der am nächsten an der Methodendefinition steht, zuerst angewendet wird, und dann werden die darüber liegenden nacheinander angewendet. Beispielsweise:

@decorator1
@decorator2
def func():
    pass

Dies ist äquivalent zu:

func = decorator1(decorator2(func))

In diesem Beispiel wird decorator2 zuerst auf func angewendet, und dann wird decorator1 auf das Ergebnis von decorator2(func) angewendet.

Die Decorator-Reihenfolge korrigieren

Lassen Sie uns unsere methods.py-Datei aktualisieren, um die Decorator-Reihenfolge zu korrigieren. Indem wir die Reihenfolge der Decorators ändern, können wir sicherstellen, dass jede Methode wie erwartet funktioniert.

from logcall import logged

class Spam:
    @logged
    def instance_method(self):
        print("Instance method called")
        return "instance result"

    @classmethod
    @logged
    def class_method(cls):
        print("Class method called")
        return "class result"

    @staticmethod
    @logged
    def static_method():
        print("Static method called")
        return "static result"

    @property
    @logged
    def property_method(self):
        print("Property method called")
        return "property result"

In dieser aktualisierten Version:

  • Bei instance_method spielt die Reihenfolge keine Rolle. Instanzmethoden werden auf einer Instanz der Klasse aufgerufen, und der @logged-Decorator kann in beliebiger Reihenfolge angewendet werden, ohne die grundlegende Funktionalität zu beeinträchtigen.
  • Bei class_method wenden wir @classmethod nach @logged an. Der @classmethod-Decorator ändert die Art und Weise, wie die Methode aufgerufen wird, und das Anwenden nach @logged stellt sicher, dass die Protokollierung korrekt funktioniert.
  • Bei static_method wenden wir @staticmethod nach @logged an. Ähnlich wie bei @classmethod hat der @staticmethod-Decorator sein eigenes Verhalten, und die Reihenfolge mit dem @logged-Decorator muss korrekt sein.
  • Bei property_method wenden wir @property nach @logged an. Dies stellt sicher, dass das Property-Verhalten beibehalten wird, während gleichzeitig die Protokollierungsfunktion erhalten bleibt.
  1. Lassen Sie uns den aktualisierten Code testen. Wir werden den gleichen Befehl wie zuvor ausführen, um zu sehen, ob die Probleme behoben sind.
cd ~/project
python3 -c "from methods import Spam; s = Spam(); print(s.instance_method()); print(Spam.class_method()); print(Spam.static_method()); print(s.property_method)"

Jetzt sollten Sie für alle Methodentypen eine korrekte Protokollierung sehen:

Calling instance_method
Instance method called
instance result
Calling class_method
Class method called
class result
Calling static_method
Static method called
static result
Calling property_method
Property method called
property result

Best Practices für Methoden-Decorators

Beim Arbeiten mit Methoden-Decorators sollten Sie die folgenden Best Practices befolgen:

  1. Wenden Sie Methoden-transformierende Decorators (@classmethod, @staticmethod, @property) nach Ihren benutzerdefinierten Decorators an. Dies stellt sicher, dass die benutzerdefinierten Decorators zuerst ihre Protokollierung oder andere Operationen ausführen können, und dann können die eingebauten Decorators die Methode wie beabsichtigt transformieren.
  2. Beachten Sie, dass die Ausführung der Decorators zur Klassen-Definition erfolgt, nicht zum Methoden-Aufruf. Dies bedeutet, dass jeder Setup- oder Initialisierungscode im Decorator ausgeführt wird, wenn die Klasse definiert wird, nicht wenn die Methode aufgerufen wird.
  3. Bei komplexeren Fällen müssen Sie möglicherweise spezialisierte Decorators für verschiedene Methodentypen erstellen. Verschiedene Methodentypen haben unterschiedliches Verhalten, und ein "einer passt für alle"-Decorator funktioniert möglicherweise nicht in allen Situationen.
✨ Lösung prüfen und üben

Erstellen eines Typüberprüfungs-Decorators mit Argumenten

In den vorherigen Schritten haben wir uns mit dem @validated-Decorator beschäftigt. Dieser Decorator wird verwendet, um Typannotationen in Python-Funktionen zu erzwingen. Typannotationen sind eine Möglichkeit, die erwarteten Typen von Funktionsargumenten und Rückgabewerten anzugeben. Jetzt gehen wir einen Schritt weiter. Wir werden einen flexibleren Decorator erstellen, der Typangaben als Argumente akzeptieren kann. Dies bedeutet, dass wir die Typen, die wir für jedes Argument und den Rückgabewert wünschen, auf eine explizitere Weise definieren können.

Das Ziel verstehen

Unser Ziel ist es, einen @enforce()-Decorator zu erstellen. Dieser Decorator ermöglicht es uns, Typbeschränkungen mithilfe von Schlüsselwortargumenten anzugeben. Hier ist ein Beispiel, wie es funktioniert:

@enforce(x=Integer, y=Integer, return_=Integer)
def add(x, y):
    return x + y

In diesem Beispiel verwenden wir den @enforce-Decorator, um anzugeben, dass die Argumente x und y der Funktion add vom Typ Integer sein sollten und auch der Rückgabewert vom Typ Integer sein sollte. Dieser Decorator verhält sich ähnlich wie unser vorheriger @validated-Decorator, aber er gibt uns mehr Kontrolle über die Typangaben.

Erstellen des enforce-Decorators

  1. Öffnen Sie zunächst die Datei validate.py in der WebIDE. Wir werden unseren neuen Decorator zu dieser Datei hinzufügen. Hier ist der Code, den wir hinzufügen werden:
from functools import wraps

class Integer:
    @classmethod
    def __instancecheck__(cls, x):
        return isinstance(x, int)

def validated(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ## Get function annotations
        annotations = func.__annotations__
        ## Check arguments against annotations
        for arg_name, arg_value in zip(func.__code__.co_varnames, args):
            if arg_name in annotations and not isinstance(arg_value, annotations[arg_name]):
                raise TypeError(f'Expected {arg_name} to be {annotations[arg_name].__name__}')

        ## Run the function and get the result
        result = func(*args, **kwargs)

        ## Check the return value
        if 'return' in annotations and not isinstance(result, annotations['return']):
            raise TypeError(f'Expected return value to be {annotations["return"].__name__}')

        return result
    return wrapper

def enforce(**type_specs):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            ## Check argument types
            for arg_name, arg_value in zip(func.__code__.co_varnames, args):
                if arg_name in type_specs and not isinstance(arg_value, type_specs[arg_name]):
                    raise TypeError(f'Expected {arg_name} to be {type_specs[arg_name].__name__}')

            ## Run the function and get the result
            result = func(*args, **kwargs)

            ## Check the return value
            if 'return_' in type_specs and not isinstance(result, type_specs['return_']):
                raise TypeError(f'Expected return value to be {type_specs["return_"].__name__}')

            return result
        return wrapper
    return decorator

Lassen Sie uns analysieren, was dieser Code tut. Die Klasse Integer wird verwendet, um einen benutzerdefinierten Typ zu definieren. Der validated-Decorator überprüft die Typen der Funktionsargumente und des Rückgabewerts anhand der Typannotationen der Funktion. Der enforce-Decorator ist der neue, den wir erstellen. Er nimmt Schlüsselwortargumente entgegen, die die Typen für jedes Argument und den Rückgabewert angeben. Innerhalb der wrapper-Funktion des enforce-Decorators überprüfen wir, ob die Typen der Argumente und des Rückgabewerts den angegebenen Typen entsprechen. Wenn nicht, werfen wir einen TypeError.

  1. Jetzt testen wir unseren neuen @enforce-Decorator. Wir werden einige Testfälle ausführen, um zu sehen, ob er wie erwartet funktioniert. Hier ist der Code, um die Tests auszuführen:
cd ~/project
python3 -c "from validate import enforce, Integer

@enforce(x=Integer, y=Integer, return_=Integer)
def add(x, y):
    return x + y

## This should work
print(add(2, 3))

## This should raise a TypeError
try:
    print(add('2', 3))
except TypeError as e:
    print(f'Error: {e}')

## This should raise a TypeError
try:
    @enforce(x=Integer, y=Integer, return_=Integer)
    def bad_add(x, y):
        return str(x + y)
    print(bad_add(2, 3))
except TypeError as e:
    print(f'Error: {e}')"

In diesem Testcode definieren wir zunächst eine add-Funktion mit dem @enforce-Decorator. Dann rufen wir die add-Funktion mit gültigen Argumenten auf, was ohne Fehler funktionieren sollte. Als nächstes rufen wir die add-Funktion mit einem ungültigen Argument auf, was einen TypeError auslösen sollte. Schließlich definieren wir eine bad_add-Funktion, die einen Wert vom falschen Typ zurückgibt, was ebenfalls einen TypeError auslösen sollte.

Wenn Sie diesen Testcode ausführen, sollten Sie eine Ausgabe ähnlich der folgenden sehen:

5
Error: Expected x to be Integer
Error: Expected return value to be Integer

Diese Ausgabe zeigt, dass unser @enforce-Decorator korrekt funktioniert. Er wirft einen TypeError, wenn die Typen der Argumente oder des Rückgabewerts nicht den angegebenen Typen entsprechen.

Vergleich der beiden Ansätze

Sowohl der @validated- als auch der @enforce-Decorator erreichen dasselbe Ziel, nämlich die Erzwingung von Typbeschränkungen, aber sie tun dies auf verschiedene Weise.

  1. Der @validated-Decorator verwendet die eingebauten Typannotationen von Python. Hier ist ein Beispiel:

    @validated
    def add(x: Integer, y: Integer) -> Integer:
        return x + y

    Mit diesem Ansatz geben wir die Typen direkt in der Funktionsdefinition mithilfe von Typannotationen an. Dies ist ein eingebautes Feature von Python und bietet bessere Unterstützung in integrierten Entwicklungsumgebungen (IDEs). IDEs können diese Typannotationen nutzen, um Codevervollständigung, Typüberprüfung und andere nützliche Funktionen bereitzustellen.

  2. Der @enforce-Decorator hingegen verwendet Schlüsselwortargumente, um die Typen anzugeben. Hier ist ein Beispiel:

    @enforce(x=Integer, y=Integer, return_=Integer)
    def add(x, y):
        return x + y

    Dieser Ansatz ist expliziter, da wir die Typangaben direkt als Argumente an den Decorator übergeben. Er kann nützlich sein, wenn man mit Bibliotheken arbeitet, die auf anderen Annotationssystemen beruhen.

Jeder Ansatz hat seine eigenen Vorteile. Typannotationen sind ein nativer Bestandteil von Python und bieten bessere IDE-Unterstützung, während der @enforce-Ansatz uns mehr Flexibilität und Explizitheit gibt. Sie können den Ansatz wählen, der am besten zu Ihren Bedürfnissen passt, je nachdem, an welchem Projekt Sie arbeiten.

✨ Lösung prüfen und üben

Zusammenfassung

In diesem Lab haben Sie gelernt, wie Sie Decorators effektiv erstellen und nutzen können. Sie haben gelernt, die Metadaten von Funktionen mit functools.wraps zu bewahren, Decorators zu erstellen, die Parameter akzeptieren, mehrere Decorators zu verarbeiten und deren Anwendungsreihenfolge zu verstehen. Sie haben auch gelernt, Decorators auf verschiedene Klassenmethoden anzuwenden und einen Typüberprüfungs-Decorator zu erstellen, der Argumente akzeptiert.

Diese Decorator-Muster werden häufig in Python-Frameworks wie Flask, Django und pytest verwendet. Das Beherrschen von Decorators ermöglicht es Ihnen, wartbareren und wiederverwendbaren Code zu schreiben. Um Ihr Lernen fortzusetzen, können Sie Context Manager (Kontextmanager), klassenbasierte Decorators, die Verwendung von Decorators für das Caching und die erweiterte Typüberprüfung mit Decorators erkunden.