Einführung
In diesem Lab werden Sie lernen, was Decorators sind und wie sie in Python funktionieren. Decorators sind eine leistungsstarke Funktion, die es Ihnen ermöglicht, das Verhalten von Funktionen zu ändern, ohne den Quellcode zu verändern. Sie werden in Python-Frameworks und -Bibliotheken weit verbreitet eingesetzt.
Sie werden auch lernen, einen einfachen Logging-Decorator zu erstellen und einen komplexeren für die Funktionsvalidierung zu implementieren. Die Dateien, die in diesem Lab verwendet werden, sind logcall.py, sample.py und validate.py, wobei validate.py modifiziert wird.
Erstellen Ihres ersten Decorators
Was sind Decorators?
In Python sind Decorators eine besondere Syntax, die für Anfänger sehr nützlich sein kann. Sie ermöglichen es Ihnen, das Verhalten von Funktionen oder Methoden zu ändern. Stellen Sie sich einen Decorator als eine Funktion vor, die eine andere Funktion als Eingabe nimmt. Sie gibt dann eine neue Funktion zurück. Diese neue Funktion erweitert oder ändert oft das Verhalten der ursprünglichen Funktion.
Decorators werden mit dem @-Symbol angewendet. Sie platzieren dieses Symbol gefolgt vom Namen des Decorators direkt über einer Funktionsdefinition. Dies ist eine einfache Möglichkeit, Python mitzuteilen, dass Sie den Decorator auf diese bestimmte Funktion anwenden möchten.
Erstellen eines einfachen Logging-Decorators
Lassen Sie uns einen einfachen Decorator erstellen, der Informationen protokolliert, wenn eine Funktion aufgerufen wird. Logging ist eine häufige Aufgabe in realen Anwendungen, und die Verwendung eines Decorators dafür ist eine gute Möglichkeit, zu verstehen, wie sie funktionieren.
Öffnen Sie zunächst den VSCode-Editor. Erstellen Sie im Verzeichnis
/home/labex/projecteine neue Datei mit dem Namenlogcall.py. In dieser Datei wird unsere Decorator-Funktion gespeichert.Fügen Sie den folgenden Code in
logcall.pyein:
## logcall.py
def logged(func):
print('Adding logging to', func.__name__)
def wrapper(*args, **kwargs):
print('Calling', func.__name__)
return func(*args, **kwargs)
return wrapper
Lassen Sie uns analysieren, was dieser Code tut:
- Die
logged-Funktion ist unser Decorator. Sie nimmt eine andere Funktion, die wirfuncnennen, als Argument. Diesefuncist die Funktion, für die wir Logging hinzufügen möchten. - Wenn der Decorator auf eine Funktion angewendet wird, wird eine Nachricht ausgegeben. Diese Nachricht teilt uns mit, dass Logging für die Funktion mit dem angegebenen Namen hinzugefügt wird.
- Innerhalb der
logged-Funktion definieren wir eine innere Funktion namenswrapper. Diesewrapper-Funktion wird die ursprüngliche Funktion ersetzen.- Wenn die dekorierte Funktion aufgerufen wird, gibt die
wrapper-Funktion eine Nachricht aus, die besagt, dass die Funktion aufgerufen wird. - Sie ruft dann die ursprüngliche Funktion (
func) mit allen Argumenten auf, die ihr übergeben wurden. Die*argsund**kwargswerden verwendet, um eine beliebige Anzahl von Positions- und Schlüsselwortargumenten zu akzeptieren. - Schließlich gibt sie das Ergebnis der ursprünglichen Funktion zurück.
- Wenn die dekorierte Funktion aufgerufen wird, gibt die
- Die
logged-Funktion gibt diewrapper-Funktion zurück. Diesewrapper-Funktion wird jetzt anstelle der ursprünglichen Funktion verwendet und fügt die Logging-Funktionalität hinzu.
Verwenden des Decorators
- Erstellen Sie nun im gleichen Verzeichnis (
/home/labex/project) eine weitere Datei mit dem Namensample.pymit dem folgenden Code:
## sample.py
from logcall import logged
@logged
def add(x, y):
return x + y
@logged
def sub(x, y):
return x - y
Die @logged-Syntax ist hier sehr wichtig. Sie teilt Python mit, den logged-Decorator auf die add- und sub-Funktionen anzuwenden. Wenn also diese Funktionen aufgerufen werden, wird die durch den Decorator hinzugefügte Logging-Funktionalität ausgeführt.
Testen des Decorators
- Um Ihren Decorator zu testen, öffnen Sie ein Terminal in VSCode. Ändern Sie zunächst das Verzeichnis in das Projektverzeichnis mit dem folgenden Befehl:
cd /home/labex/project
Starten Sie dann den Python-Interpreter:
python3
- Importieren Sie im Python-Interpreter das
sample-Modul und testen Sie die dekorierten Funktionen:
>>> import sample
Adding logging to add
Adding logging to sub
>>> sample.add(3, 4)
Calling add
7
>>> sample.sub(2, 3)
Calling sub
-1
>>> exit()
Beachten Sie, dass beim Importieren des sample-Moduls die Nachrichten "Adding logging to..." ausgegeben werden. Dies liegt daran, dass der Decorator angewendet wird, wenn das Modul importiert wird. Jedes Mal, wenn Sie eine der dekorierten Funktionen aufrufen, wird die "Calling..."-Nachricht ausgegeben. Dies zeigt, dass der Decorator wie erwartet funktioniert.
Dieser einfache Decorator demonstriert das grundlegende Konzept von Decorators. Er umhüllt die ursprüngliche Funktion mit zusätzlicher Funktionalität (in diesem Fall Logging), ohne den Code der ursprünglichen Funktion zu ändern. Dies ist eine leistungsstarke Funktion in Python, die Sie in vielen verschiedenen Szenarien nutzen können.
Erstellen eines Validierungs-Decorators
In diesem Schritt werden wir einen praktischeren Decorator erstellen. Ein Decorator in Python ist eine spezielle Art von Funktion, die das Verhalten einer anderen Funktion ändern kann. Der von uns zu erstellende Decorator wird die Funktionsargumente anhand von Typannotationen validieren. Typannotationen sind eine Möglichkeit, die erwarteten Datentypen der Argumente und des Rückgabewerts einer Funktion anzugeben. Dies ist ein häufiger Anwendungsfall in realen Anwendungen, da es hilft sicherzustellen, dass Funktionen die richtigen Eingabetypen erhalten, was viele Fehler vermeiden kann.
Verständnis der Validierungsklassen
Wir haben bereits eine Datei namens validate.py für Sie erstellt, die einige Validierungsklassen enthält. Validierungsklassen werden verwendet, um zu prüfen, ob ein Wert bestimmte Kriterien erfüllt. Um sehen zu können, was in dieser Datei enthalten ist, müssen Sie sie im VSCode-Editor öffnen. Sie können dies tun, indem Sie die folgenden Befehle im Terminal ausführen:
cd /home/labex/project
code validate.py
Die Datei enthält drei Klassen:
Validator- Dies ist eine Basisklasse. Eine Basisklasse bietet ein allgemeines Gerüst oder eine Struktur, von der andere Klassen erben können. In diesem Fall bietet sie die grundlegende Struktur für die Validierung.Integer- Diese Validator-Klasse wird verwendet, um sicherzustellen, dass ein Wert eine Ganzzahl ist. Wenn Sie einem Funktionsaufruf, der diesen Validator verwendet, einen Wert übergeben, der keine Ganzzahl ist, wird ein Fehler ausgelöst.PositiveInteger- Diese Validator-Klasse stellt sicher, dass ein Wert eine positive Ganzzahl ist. Wenn Sie also eine negative Ganzzahl oder Null übergeben, wird ebenfalls ein Fehler ausgelöst.
Hinzufügen des Validierungs-Decorators
Jetzt werden wir der Datei validate.py eine Decorator-Funktion namens validated hinzufügen. Dieser Decorator wird mehrere wichtige Aufgaben ausführen:
- Er wird die Typannotationen einer Funktion untersuchen. Typannotationen sind wie kleine Hinweise, die uns sagen, welche Art von Daten die Funktion erwartet.
- Er wird die an die Funktion übergebenen Argumente anhand dieser Typannotationen validieren. Das bedeutet, er wird prüfen, ob die an die Funktion übergebenen Werte vom richtigen Typ sind.
- Er wird auch den Rückgabewert der Funktion anhand seiner Annotation validieren. Somit wird sichergestellt, dass die Funktion den Datentyp zurückgibt, den sie zurückgeben soll.
- Wenn die Validierung fehlschlägt, wird er informative Fehlermeldungen ausgeben. Diese Meldungen werden Ihnen genau sagen, was schief gelaufen ist, z. B. welches Argument den falschen Typ hatte.
Fügen Sie den folgenden Code ans Ende der Datei validate.py hinzu:
## Add to validate.py
import inspect
import functools
def validated(func):
sig = inspect.signature(func)
print(f'Validating {func.__name__} {sig}')
@functools.wraps(func)
def wrapper(*args, **kwargs):
## Bind arguments to the signature
bound = sig.bind(*args, **kwargs)
errors = []
## Validate each argument
for name, value in bound.arguments.items():
if name in sig.parameters:
param = sig.parameters[name]
if param.annotation != inspect.Parameter.empty:
try:
## Create an instance of the validator and validate the value
if isinstance(param.annotation, type) and issubclass(param.annotation, Validator):
validator = param.annotation()
bound.arguments[name] = validator.validate(value)
except Exception as e:
errors.append(f' {name}: {e}')
## If validation errors, raise an exception
if errors:
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
## Call the function
result = func(*bound.args, **bound.kwargs)
## Validate the return value
if sig.return_annotation != inspect.Signature.empty:
try:
if isinstance(sig.return_annotation, type) and issubclass(sig.return_annotation, Validator):
validator = sig.return_annotation()
result = validator.validate(result)
except Exception as e:
raise TypeError(f'Bad return: {e}') from None
return result
return wrapper
Dieser Code verwendet das inspect-Modul von Python. Das inspect-Modul ermöglicht es uns, Informationen über lebende Objekte wie Funktionen zu erhalten. Hier verwenden wir es, um die Signatur der Funktion zu untersuchen und die Argumente anhand von Typannotationen zu validieren. Wir verwenden auch functools.wraps. Dies ist eine Hilfsfunktion, die die Metadaten der ursprünglichen Funktion, wie ihren Namen und ihre Docstring, beibehält. Metadaten sind wie zusätzliche Informationen über die Funktion, die uns helfen, zu verstehen, was sie tut.
Testen des Validierungs-Decorators
Lassen Sie uns eine Datei erstellen, um unseren Validierungs-Decorator zu testen. Wir werden eine neue Datei namens test_validate.py erstellen und den folgenden Code hinzufügen:
## test_validate.py
from validate import Integer, PositiveInteger, validated
@validated
def add(x: Integer, y: Integer) -> Integer:
return x + y
@validated
def pow(x: Integer, y: Integer) -> Integer:
return x ** y
## Test with a class
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@property
def cost(self):
return self.shares * self.price
@validated
def sell(self, nshares: PositiveInteger):
self.shares -= nshares
Jetzt werden wir unseren Decorator im Python-Interpreter testen. Navigieren Sie zunächst in das Projektverzeichnis und starten Sie den Python-Interpreter, indem Sie die folgenden Befehle im Terminal ausführen:
cd /home/labex/project
python3
Dann können wir im Python-Interpreter den folgenden Code ausführen, um unseren Decorator zu testen:
>>> from test_validate import add, pow, Stock
Validating add (x: validate.Integer, y: validate.Integer) -> validate.Integer
Validating pow (x: validate.Integer, y: validate.Integer) -> validate.Integer
Validating sell (self, nshares: validate.PositiveInteger) -> <class 'inspect._empty'>
>>>
>>> ## Test with valid inputs
>>> add(2, 3)
5
>>>
>>> ## Test with invalid inputs
>>> add('2', '3')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/labex/project/validate.py", line 75, in wrapper
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
TypeError: Bad Arguments
x: Expected <class 'int'>
y: Expected <class 'int'>
>>>
>>> ## Test valid power
>>> pow(2, 3)
8
>>>
>>> ## Test with negative exponent (produces non - integer result)
>>> pow(2, -1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/labex/project/validate.py", line 83, in wrapper
raise TypeError(f'Bad return: {e}') from None
TypeError: Bad return: Expected <class 'int'>
>>>
>>> ## Test with a class
>>> s = Stock("GOOG", 100, 490.1)
>>> s.sell(50)
>>> s.shares
50
>>>
>>> ## Test with invalid shares
>>> s.sell(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/labex/project/validate.py", line 75, in wrapper
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
TypeError: Bad Arguments
nshares: Expected value > 0
>>> exit()
Wie Sie sehen können, hat unser validated-Decorator die Typüberprüfung von Funktionsargumenten und Rückgabewerten erfolgreich durchgeführt. Dies ist sehr nützlich, da es unseren Code robuster macht. Anstatt zuzulassen, dass Typfehler tiefer in den Code vordringen und schwer zu findende Fehler verursachen, fangen wir sie an den Funktionsgrenzen ab.
Zusammenfassung
In diesem Lab haben Sie sich mit Decorators in Python vertraut gemacht, einschließlich ihrer Definition und ihres Funktionsprinzips. Sie haben auch gelernt, wie Sie einen einfachen Logging-Decorator erstellen, um Verhalten zu Funktionen hinzuzufügen, und einen komplexeren Decorator für die Validierung von Funktionsargumenten anhand von Typannotationen entwickelt. Darüber hinaus haben Sie gelernt, das inspect-Modul zur Analyse von Funktionssignaturen und functools.wraps zur Aufrechterhaltung von Funktionsmetadaten zu verwenden.
Decorators sind eine leistungsstarke Python-Funktion, die es ermöglicht, wartbareren und wiederverwendbaren Code zu schreiben. Sie werden häufig in Python-Frameworks und -Bibliotheken für Querschnittsfunktionen wie Logging, Zugriffskontrolle und Caching eingesetzt. Sie können diese Techniken nun in Ihren eigenen Python-Projekten anwenden, um sauberen und wartbaren Code zu schreiben.