Closed5

pythonのdecorator周りのtype hintについて調べる

名無し。名無し。

疑問点

decorator周りのtype hintで引数にとった関数のtype hintをdecorator側のtype hintが上書きしないようにできないんだろうか?

python3.10を前提に調べる。

何に使うんだろうと思ってたけど、ハマってる事案の解決策になりそうなので調べる。

参考資料

  1. https://peps.python.org/pep-0612/
  2. https://docs.python.org/3.10/library/typing.html
  3. https://docs.python.org/3.10/library/typing.html#typing.ParamSpec
名無し。名無し。

関数のシグネチャの type hint として collections.abc.Callable がある。これは PEP484 で定義されてる。

具体的には、以下のように使う。

from collections.abc import Callable

def hoge(a: int = 10, b: dict[str, int] | None = None ) -> int:
   return 1

def fuga(fn: Callable[[int, dict[str, int] | None], int]) -> None:
  fn()

ただ、関数を引数として取るときに具体的な型がわからないこともある。
これを解決する方法は、ParamSpec が追加される前は、elipsis を使ってtype hint を書くことだったらしい。

from typing import TypeVar
from collections.abc import Callable

def hoge(a: int, b: dict[str, int]) -> int:
   return 1

def fuga(fn: Callable[..., int]) -> None:
  fn()

ただし、この方法だと decorator の定義の時に以下のような時に問題が生じる。

例は参考資料3. を参考にして elipsisを使った定義に変更してある。

from typing import Any, TypeVar
from collections.abc import Callable
import logging

AnyCallableT = TypeVar("AnyCallableT", bound=Callable[..., Any])

def add_logging(f: AnyCallableT) -> AnyCalla![](https://storage.googleapis.com/zenn-user-upload/2f116dc0c9df-20240124.png)
bleT:
    '''A type-safe decorator to add logging to a function.'''
    def inner(*args, **kwargs) -> Any:
        logging.info(f'{f.__name__} was called')
        return f(*args, **kwargs)
    return inner

@add_logging
def add_two(x: float, y: float) -> float:
    '''Add two numbers together.'''
    return x + y

この例では、inner 関数の引数は Any とされるべきになってしまうことと、 add_logging の内部で typing.cast 関数を呼ぶか、return inner のエラーを無視するように静的解析に教える必要がある。

実際にpyrightにかけて、解析してみるとアサインできないエラーが起きるのでcast/type: ignoreが必要になりそうなことがわかる。(使用pyright version: 1.1.329)

名無し。名無し。

というわけで、以上のような時に問題になる部分を解決するのが ParamsSpec というらしい。

エディタに、参考資料3.の例をそのまま貼り付けて add_two 関数を呼び出してみた。

さっきの課題点クリアしてそう。

ここまで調べてさっきの大元の問題は従来の方法だと解決してるかチェックしてなかったのに気づいたので従来の方法でもデコレートされた関数が呼び出し時にちゃんと関数側の type hintで解析されてるかチェックしてみた

ちゃんとチェックされてそう。自分の疑問点はどちらの方法でもクリアできそうだけど ParamSpec 使えるなら使って解決した方がエラー握り潰さず済んで良さそう。

名無し。名無し。

https://github.com/aws-powertools/powertools-lambda-python/blob/develop/aws_lambda_powertools/tracing/tracer.py#L247

という訳で、上のリンク先の capture_lambda_handler メソッドの lambda_handler の type hintはこのファイルでインポートされてる AnyCallableT を使う方法の方が、event_source とかで引数を事前にパースしておく時に type hint を parse した型で書けるの良くないか?という疑問点が晴れたのでした。(他に見落としててこうなってるかもなのでもう少し調査してみる)

このスクラップは2024/01/24にクローズされました