Python Decorators verstehen

PythonPythonBeginner
Jetzt üben

Einführung

In diesem Lab erlangen Sie ein umfassendes Verständnis von Decorators (Dekoratoren) in Python, einem mächtigen Feature zur Modifikation oder Erweiterung von Funktionen und Methoden. Wir beginnen mit der Einführung des fundamentalen Konzepts von Decorators und erkunden deren grundlegende Anwendung anhand praktischer Beispiele.

Aufbauend auf dieser Grundlage lernen Sie, wie Sie functools.wraps effektiv nutzen, um wichtige Metadaten der dekorierten Funktion zu erhalten. Anschließend tauchen wir in spezifische Decorators wie den property-Decorator ein und verstehen dessen Rolle bei der Verwaltung des Attributzugriffs. Abschließend wird das Lab die Unterschiede zwischen Instanzmethoden, Klassenmethoden und statischen Methoden verdeutlichen und demonstrieren, wie Decorators in diesen Kontexten verwendet werden, um das Verhalten von Methoden innerhalb von Klassen zu steuern.

Dies ist ein Guided Lab, das schrittweise Anweisungen bietet, um Ihnen beim Lernen und Üben zu helfen. Befolgen Sie die Anweisungen sorgfältig, um jeden Schritt abzuschließen und praktische Erfahrungen zu sammeln. Historische Daten zeigen, dass dies ein Labor der Stufe Anfänger mit einer Abschlussquote von 93% ist. Es hat eine positive Bewertungsrate von 100% von den Lernenden erhalten.

Grundlagen von Decorators verstehen

In diesem Schritt führen wir das Konzept der Decorators ein und zeigen deren grundlegende Verwendung. Ein Decorator ist eine Funktion, die eine andere Funktion als Argument entgegennimmt, zusätzliche Funktionalität hinzufügt und eine weitere Funktion zurückgibt, alles ohne den Quellcode der ursprünglichen Funktion zu verändern.

Suchen Sie zunächst im Dateiexplorer auf der linken Seite des WebIDE nach der Datei decorator_basics.py. Doppelklicken Sie darauf, um sie zu öffnen. Wir werden unseren ersten Decorator in dieser Datei schreiben.

Kopieren Sie den folgenden Code und fügen Sie ihn in decorator_basics.py ein:

import datetime

def log_activity(func):
    """A simple decorator to log function calls."""
    def wrapper(*args, **kwargs):
        print(f"Calling function '{func.__name__}' at {datetime.datetime.now()}")
        result = func(*args, **kwargs)
        print(f"Function '{func.__name__}' finished.")
        return result
    return wrapper

@log_activity
def greet(name):
    """A simple function to greet someone."""
    print(f"Hello, {name}!")

## Call the decorated function
greet("Alice")

## Let's inspect the function's metadata
print(f"\nFunction name: {greet.__name__}")
print(f"Function docstring: {greet.__doc__}")

Lassen Sie uns diesen Code analysieren:

  • Wir definieren eine Decorator-Funktion log_activity, die eine Funktion func als Argument akzeptiert.
  • Innerhalb von log_activity definieren wir eine verschachtelte Funktion wrapper. Diese Funktion enthält das neue Verhalten. Sie gibt eine Log-Nachricht aus, ruft die ursprüngliche Funktion func auf und gibt dann eine weitere Log-Nachricht aus.
  • Die Funktion log_activity gibt die wrapper-Funktion zurück.
  • Die Syntax @log_activity über der Funktion greet ist eine Abkürzung für greet = log_activity(greet). Sie wendet unseren Decorator auf die Funktion greet an.

Speichern Sie nun die Datei (Sie können Strg+S oder Cmd+S verwenden). Um das Skript auszuführen, öffnen Sie das integrierte Terminal am unteren Rand des WebIDE und führen Sie den folgenden Befehl aus:

python ~/project/decorator_basics.py

Sie werden die folgende Ausgabe sehen. Beachten Sie, dass das Datum und die Uhrzeit variieren werden.

Calling function 'greet' at 2023-10-27 10:30:00.123456
Hello, Alice!
Function 'greet' finished.

Function name: wrapper
Function docstring: None

Beachten Sie zwei Dinge in der Ausgabe. Erstens wird unsere Funktion greet nun mit den Protokollierungsnachrichten umhüllt. Zweitens wurden der Name und das Docstring der Funktion durch die der wrapper-Funktion ersetzt. Dies kann für das Debugging und die Introspektion problematisch sein. Im nächsten Schritt lernen wir, wie man dies behebt.

Beibehaltung der Funktionsmetadaten mit functools.wraps

Im vorherigen Schritt haben wir beobachtet, dass das Dekorieren einer Funktion deren ursprüngliche Metadaten (wie __name__ und __doc__) mit den Metadaten der Wrapper-Funktion überschreibt. Pythons Modul functools bietet hierfür eine Lösung: den Decorator wraps.

Der Decorator wraps wird innerhalb Ihres eigenen Decorators verwendet, um die Metadaten von der ursprünglichen Funktion auf die Wrapper-Funktion zu kopieren.

Lassen Sie uns unseren Code in decorator_basics.py ändern. Öffnen Sie die Datei im WebIDE und aktualisieren Sie sie, um functools.wraps zu verwenden.

import datetime
from functools import wraps

def log_activity(func):
    """A simple decorator to log function calls."""
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling function '{func.__name__}' at {datetime.datetime.now()}")
        result = func(*args, **kwargs)
        print(f"Function '{func.__name__}' finished.")
        return result
    return wrapper

@log_activity
def greet(name):
    """A simple function to greet someone."""
    print(f"Hello, {name}!")

## Call the decorated function
greet("Alice")

## Let's inspect the function's metadata again
print(f"\nFunction name: {greet.__name__}")
print(f"Function docstring: {greet.__doc__}")

Die einzigen Änderungen sind:

  1. Wir haben wraps aus dem Modul functools importiert.
  2. Wir haben @wraps(func) direkt über der Definition unserer wrapper-Funktion hinzugefügt.

Speichern Sie die Datei und führen Sie sie erneut im Terminal aus:

python ~/project/decorator_basics.py

Nun wird die Ausgabe anders sein:

Calling function 'greet' at 2023-10-27 10:35:00.543210
Hello, Alice!
Function 'greet' finished.

Function name: greet
Function docstring: A simple function to greet someone.

Wie Sie sehen können, wird der Funktionsname korrekt als greet gemeldet und das ursprüngliche Docstring bleibt erhalten. Die Verwendung von functools.wraps ist eine Best Practice, die Ihre Decorators robuster und professioneller macht.

Implementierung verwalteter Attribute mit @property

Python bietet mehrere eingebaute Decorators. Einer der nützlichsten ist @property, der es ermöglicht, eine Klassenmethode in ein „verwaltetes Attribut“ (managed attribute) umzuwandeln. Dies ist ideal, um Logik wie Validierung oder Berechnung beim Attributzugriff hinzuzufügen, ohne die Art und Weise zu ändern, wie Benutzer mit Ihrer Klasse interagieren.

Lassen Sie uns dies anhand der Erstellung einer Circle-Klasse untersuchen. Öffnen Sie die Datei property_decorator.py im Dateiexplorer.

Kopieren Sie den folgenden Code und fügen Sie ihn in property_decorator.py ein:

import math

class Circle:
    def __init__(self, radius):
        ## The actual value is stored in a "private" attribute
        self._radius = radius

    @property
    def radius(self):
        """The radius property."""
        print("Getting radius...")
        return self._radius

    @radius.setter
    def radius(self, value):
        """The radius setter with validation."""
        print(f"Setting radius to {value}...")
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @property
    def area(self):
        """A read-only computed property for the area."""
        print("Calculating area...")
        return math.pi * self._radius ** 2

## --- Let's test our Circle class ---
c = Circle(5)

## Access the radius like a normal attribute (triggers the getter)
print(f"Initial radius: {c.radius}\n")

## Change the radius (triggers the setter)
c.radius = 10
print(f"New radius: {c.radius}\n")

## Access the computed area property
print(f"Circle area: {c.area:.2f}\n")

## Try to set an invalid radius (triggers the setter's validation)
try:
    c.radius = -2
except ValueError as e:
    print(f"Error: {e}")

In diesem Code:

  • @property über der Methode radius definiert einen „Getter“. Er wird aufgerufen, wenn Sie auf c.radius zugreifen.
  • @radius.setter definiert einen „Setter“ für die Eigenschaft radius. Er wird aufgerufen, wenn Sie einen Wert zuweisen, z. B. c.radius = 10. Wir haben hier eine Validierung hinzugefügt, um negative Werte zu verhindern.
  • Die Methode area verwendet ebenfalls @property, hat aber keinen Setter, wodurch sie zu einem schreibgeschützten Attribut wird. Ihr Wert wird jedes Mal neu berechnet, wenn darauf zugegriffen wird.

Speichern Sie die Datei und führen Sie sie im Terminal aus:

python ~/project/property_decorator.py

Sie sollten die folgende Ausgabe sehen, die demonstriert, wie der Getter, Setter und die Validierungslogik automatisch aufgerufen werden:

Getting radius...
Initial radius: 5

Setting radius to 10...
Getting radius...
New radius: 10

Calculating area...
Circle area: 314.16

Setting radius to -2...
Error: Radius cannot be negative

Unterscheidung zwischen Instanz-, Klassen- und statischen Methoden

In Python-Klassen können Methoden an eine Instanz, die Klasse oder gar nicht gebunden sein. Decorators werden verwendet, um diese verschiedenen Methodentypen zu definieren.

  • Instanzmethoden (Instance Methods): Der Standardtyp. Sie erhalten die Instanz als erstes Argument, das konventionell self genannt wird. Sie arbeiten mit instanzspezifischen Daten.
  • Klassenmethoden (Class Methods): Gekennzeichnet mit @classmethod. Sie erhalten die Klasse als erstes Argument, das konventionell cls genannt wird. Sie arbeiten mit klassenebenen Daten und werden oft als alternative Konstruktoren verwendet.
  • Statische Methoden (Static Methods): Gekennzeichnet mit @staticmethod. Sie erhalten kein spezielles erstes Argument. Sie sind im Wesentlichen reguläre Funktionen, die innerhalb einer Klasse benannt sind, und können nicht auf den Instanz- oder Klassenstatus zugreifen.

Sehen wir uns alle drei in Aktion an. Öffnen Sie die Datei class_methods.py im Dateiexplorer.

Kopieren Sie den folgenden Code und fügen Sie ihn in class_methods.py ein:

class MyClass:
    class_variable = "I am a class variable"

    def __init__(self, instance_variable):
        self.instance_variable = instance_variable

    ## 1. Instance Method
    def instance_method(self):
        print("\n--- Calling Instance Method ---")
        print(f"Can access instance data: self.instance_variable = '{self.instance_variable}'")
        print(f"Can access class data: self.class_variable = '{self.class_variable}'")

    ## 2. Class Method
    @classmethod
    def class_method(cls):
        print("\n--- Calling Class Method ---")
        print(f"Can access class data: cls.class_variable = '{cls.class_variable}'")
        ## Note: Cannot access instance_variable without an instance
        print("Cannot access instance data directly.")

    ## 3. Static Method
    @staticmethod
    def static_method(a, b):
        print("\n--- Calling Static Method ---")
        print("Cannot access instance or class data directly.")
        print(f"Just a utility function: {a} + {b} = {a + b}")

## --- Let's test the methods ---
## Create an instance of the class
my_instance = MyClass("I am an instance variable")

## Call the instance method (requires an instance)
my_instance.instance_method()

## Call the class method (can be called on the class or an instance)
MyClass.class_method()
my_instance.class_method() ## Also works

## Call the static method (can be called on the class or an instance)
MyClass.static_method(10, 5)
my_instance.static_method(20, 8) ## Also works

Speichern Sie die Datei und führen Sie sie im Terminal aus:

python ~/project/class_methods.py

Untersuchen Sie die Ausgabe sorgfältig. Sie demonstriert klar die Fähigkeiten und Einschränkungen jeder Methodentypologie.

--- Calling Instance Method ---
Can access instance data: self.instance_variable = 'I am an instance variable'
Can access class data: self.class_variable = 'I am a class variable'

--- Calling Class Method ---
Can access class data: cls.class_variable = 'I am a class variable'
Cannot access instance data directly.

--- Calling Class Method ---
Can access class data: cls.class_variable = 'I am a class variable'
Cannot access instance data directly.

--- Calling Static Method ---
Cannot access instance or class data directly.
Just a utility function: 10 + 5 = 15

--- Calling Static Method ---
Cannot access instance or class data directly.
Just a utility function: 20 + 8 = 28

Dieses Beispiel bietet eine klare Referenz dafür, wann jeder Methodentyp verwendet werden sollte, basierend darauf, ob er Zugriff auf den Instanzstatus, den Klassenstatus oder keinen von beiden benötigt.

Zusammenfassung

In diesem Lab haben Sie ein praktisches Verständnis von Decorators in Python erlangt. Sie begannen damit, zu lernen, wie man einen Basis-Decorator erstellt und anwendet, um einer Funktion zusätzliche Funktionalität hinzuzufügen. Anschließend sahen Sie die Wichtigkeit der Verwendung von functools.wraps, um die Metadaten der ursprünglichen Funktion zu erhalten – eine entscheidende Best Practice für das Schreiben sauberer und wartbarer Decorators.

Darüber hinaus haben Sie leistungsstarke eingebaute Decorators erkundet. Sie lernten, den @property-Decorator zu verwenden, um verwaltete Attribute mit benutzerdefinierter Getter- und Setter-Logik zu erstellen, was Funktionen wie die Eingabevalidierung ermöglicht. Schließlich haben Sie zwischen Instanzmethoden, Klassenmethoden (@classmethod) und statischen Methoden (@staticmethod) unterschieden und verstanden, wie jede im Rahmen einer Klassenstruktur basierend auf ihrem Zugriff auf den Instanz- und Klassenstatus einen anderen Zweck erfüllt.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.