Heim > Artikel > Backend-Entwicklung > Vollständig typisierter Python-Dekorator mit Parametern
Der in diesem kurzen Artikel gezeigte Code stammt aus meinem kleinen, vertraglich entworfenen Open-Source-Projekt, das einen typisierten Dekorator bereitstellt. Dekorateure sind ein sehr nützliches Konzept und Sie werden online auf jeden Fall viel darüber finden. Einfach ausgedrückt ermöglichen sie die Ausführung von Code jedes Mal (vor und nach) dem Aufruf der dekorierten Funktion. Auf diese Weise können Sie Funktionsparameter oder Rückgabewerte ändern, Ausführungszeiten messen, Protokollierung hinzufügen, Typprüfungen zur Ausführungszeit durchführen und vieles mehr. Beachten Sie, dass Dekoratoren auch für Klassen geschrieben werden können, was einen anderen Metaprogrammierungsansatz bietet (z. B. im attrs-Paket).
In seiner einfachsten Form wird ein Dekorator wie der folgende Code definiert:
def my_first_decorator(func): def wrapped(*args, **kwargs): # do something before result = func(*args, **kwargs) # do something after return result return wrapped @my_first_decorator def func(a): return a
Wie der obige Code, denn wenn a Wenn eine umschlossene verschachtelte Funktion definiert ist, kann auf die sie umgebenden Variablen innerhalb der Funktion zugegriffen werden und sie werden im Speicher gehalten, solange die Funktion irgendwo verwendet wird (dies wird in funktionalen Programmiersprachen als Abschluss bezeichnet).
Sehr einfach, aber das hat einige Nachteile. Das größte Problem besteht darin, dass die geänderte Funktion ihren vorherigen Funktionsnamen (Sie können dies mit inspect.signature sehen), ihre Dokumentzeichenfolge und sogar ihren Namen verliert. Dies sind Probleme mit Quellcode-Dokumentationstools (z. B. Sphinx), aber das kann passieren lässt sich leicht mit dem Dekorator functools.wraps aus der Standardbibliothek lösen:
from functools import wraps from typing import Any, Callable, TypeVar, ParamSpec P = ParamSpec("P") # 需要python >= 3.10 R = TypeVar("R") def my_second_decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapped(*args: Any, **kwargs: Any) -> R: # do something before result = func(*args, **kwargs) # do something after return result return wrapped @my_second_decorator def func2(a: int) -> int: """Does nothing""" return a print(func2.__name__) # 'func2' print(func2.__doc__) # 'Does nothing'
In diesem Beispiel habe ich Typanmerkungen hinzugefügt. Anmerkungen und Typhinweise sind die wichtigsten Ergänzungen, die an Python vorgenommen wurden. Bessere Lesbarkeit, Code-Vervollständigung in der IDE und Wartbarkeit größerer Codebasen sind nur einige Beispiele. Der obige Code sollte bereits die meisten Anwendungsfälle abdecken, der Dekorator kann jedoch nicht parametrisiert werden. Erwägen Sie, einen Dekorator zu schreiben, der die Ausführungszeit einer Funktion aufzeichnet, jedoch nur, wenn diese eine bestimmte Anzahl von Sekunden überschreitet. Diese Nummer sollte für jede dekorierte Funktion individuell konfigurierbar sein. Wenn nicht angegeben, sollte der Standardwert verwendet werden und der Dekorator sollte ohne Klammern verwendet werden, um die Verwendung zu vereinfachen:
@time(threshold=2) def func1(a): ... # No paranthesis when using default threshold @time def func2(b): ...
Wenn Sie im zweiten Fall Klammern verwenden können, oder geben Sie keinen Standardwert für an Parameter überhaupt, dann reicht dieser Tipp aus:
from functools import wraps from typing import Any, Callable, TypeVar, ParamSpec P = ParamSpec("P") # 需要python >= 3.10 R = TypeVar("R") def my_third_decorator(threshold: int = 1) -> Callable[[Callable[P, R]], Callable[P, R]]: def decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> R: # do something before you can use `threshold` result = func(*args, **kwargs) # do something after return result return wrapper return decorator @my_third_decorator(threshold=2) def func3a(a: int) -> None: ... # works @my_third_decorator() def func3b(a: int) -> None: ... # Does not work! @my_third_decorator def func3c(a: int) -> None: ...
Um den dritten Fall abzudecken, gibt es Pakete, nämlich Wraps und Decorator, die tatsächlich mehr können, als nur optionale Parameter hinzuzufügen. Obwohl die Qualität sehr hoch ist, bringen sie einiges an zusätzlicher Komplexität mit sich. Bei Verwendung der mit „wrapt“ dekorierten Funktion traten außerdem Serialisierungsprobleme auf, wenn die Funktion auf einem Remote-Cluster ausgeführt wurde. Soweit ich weiß, ist keines von beiden vollständig typisiert, daher schlagen statische Typprüfer/Linters (wie mypy) im strikten Modus fehl.
Ich musste diese Probleme lösen, als ich an meinem eigenen Paket arbeitete und beschloss, meine eigene Lösung zu schreiben. Es wird zu einem Muster, das leicht wiederverwendbar ist, sich aber nur schwer in eine Bibliothek umwandeln lässt.
Es verwendet überladene Dekoratoren aus der Standardbibliothek. Auf diese Weise kann derselbe Dekorator zur Verwendung mit unserem parameterlosen Dekorator angegeben werden. Ansonsten handelt es sich um eine Kombination der beiden obigen Ausschnitte. Ein Nachteil dieses Ansatzes besteht darin, dass alle Parameter als Schlüsselwortargumente angegeben werden müssen (dies erhöht schließlich die Lesbarkeit). aber die Vorteile überwiegen die Kosten.
Originaltext:
https://www.php.cn/link/d0f82e1046ccbd597c7f2a7bfba9e7dd
Das obige ist der detaillierte Inhalt vonVollständig typisierter Python-Dekorator mit Parametern. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!