この短い記事に示されているコードは、契約によって設計された私の小さなオープンソース プロジェクトから抜粋されたもので、型付きデコレーターを提供します。デコレータは非常に便利な概念であり、オンラインで多くの情報を見つけることができます。簡単に言えば、装飾された関数が呼び出されるたびに (前後に) コードを実行できるようになります。このようにして、関数のパラメーターや戻り値の変更、実行時間の測定、ログの追加、実行時の型チェックの実行などを行うことができます。デコレータはクラス用に作成することもでき、別のメタプログラミング アプローチ (attrs パッケージで行われるような) を提供することもできることに注意してください。
最も単純な形式では、デコレータ定義は次のコードのようになります。
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
上記のコードは、ラップされた入れ子関数が定義されると、その関数がどこかで使用されている限り、周囲の変数に関数内でアクセスしてメモリに保存できるためです (関数型プログラミング言語ではこれをクロージャと呼びます)。 )。
シンプルですが、これにはいくつかの欠点があります。最大の問題は、装飾された関数が以前の関数名 (inspect.signature で確認できます)、ドキュメント文字列、さらにはその名前さえも失うことです。これらはソース コード ドキュメント ツール (sphinx など) の問題ですが、場合によっては、これは、標準ライブラリの functools.wraps デコレータを使用すると簡単に解決できます。
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'
この例では、型アノテーションを追加しました。アノテーションと型ヒントは Python に対して行われます。追加。可読性の向上、IDE でのコード補完、大規模なコード ベースの保守性はほんの一例です。上記のコードはほとんどのユースケースをすでにカバーしているはずですが、デコレータをパラメータ化することはできません。関数の実行時間を、一定の秒数を超えた場合にのみ記録するデコレータを作成することを検討してください。この数値は、装飾された関数ごとに個別に構成できる必要があります。指定しない場合は、デフォルト値を使用し、使いやすくするためにデコレーターを括弧なしで使用する必要があります。
@time(threshold=2) def func1(a): ... # No paranthesis when using default threshold @time def func2(b): ...
2 番目のケースで括弧を使用できる場合、または指定しないでください。パラメータのデフォルト値がまったくない場合は、このレシピで十分です:
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: ...
3 番目のケースをカバーするには、使用可能な Select パラメータを追加するだけでなく、実際にはそれ以上のことを実行できるパッケージ、つまりラップとデコレータがあります。品質は非常に高いですが、かなりの複雑さが追加されます。 Wrapt で装飾された関数を使用すると、リモート クラスターで関数を実行するときにシリアル化の問題がさらに発生しました。私の知る限り、どちらも完全に型指定されていないため、静的型チェッカー/リンター (mypy など) は厳密モードでは失敗します。
私が独自のパッケージに取り組み、独自のソリューションを作成することにしたとき、これらの問題を解決する必要がありました。再利用はしやすいがライブラリ化が難しいパターンとなります。
標準ライブラリのオーバーロードされたデコレータを使用します。このようにして、同じデコレータをパラメータのないデコレータで使用するように指定できます。それ以外は、上記の 2 つのスニペットを組み合わせたものです。このアプローチの欠点の 1 つは、すべてのパラメーターをキーワード引数として指定する必要があることです (これにより、最終的に読みやすさが向上します)
from typing import Callable, TypeVar, ParamSpec from functools import partial, wraps P = ParamSpec("P") # requires python >= 3.10 R = TypeVar("R @overload def typed_decorator(func: Callable[P, R]) -> Callable[P, R]: ... @overload def typed_decorator(*, first: str = "x", second: bool = True) -> Callable[[Callable[P, R]], Callable[P, R]]: ... def typed_decorator( func: Optional[Callable[P, R]] = None, *, first: str = "x", second: bool = True ) -> Union[Callable[[Callable[P, R]], Callable[P, R]], Callable[P, R]]: """ Describe what the decorator is supposed to do! Parameters ---------- first : str, optional First argument, by default "x". This is a keyword-only argument! second : bool, optional Second argument, by default True. This is a keyword-only argument! """ def wrapper(func: Callable[P, R], *args: Any, **kw: Any) -> R: """The actual logic""" # Do something with first and second and produce a `result` of type `R` return result # Without arguments `func` is passed directly to the decorator if func is not None: if not callable(func): raise TypeError("Not a callable. Did you use a non-keyword argument?") return wraps(func)(partial(wrapper, func)) # With arguments, we need to return a function that accepts the function def decorator(func: Callable[P, R]) -> Callable[P, R]: return wraps(func)(partial(wrapper, func)) return decorator
後で、パラメーターのデコレーターなしで個別に使用できるようになります。
@typed_decorator def spam(a: int) -> int: return a @typed_decorator(first = "y def eggs(a: int) -> int: return a
https://www.php.cn/link/d0f82e1046ccbd597c7f2a7bfba9e7dd
以上がパラメータを備えた完全に型指定された Python デコレータの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。