簡體   English   中英

Python inspect.Signature from typing.Callable

[英]Python inspect.Signature from typing.Callable

如何將typing.Callable類型提示轉換為inspect.Signature function signature object?

用例

假設我有一個 function 簽名hint

hint = typing.Callable[[int], float]

我想將它用於類型提示以及查找符合hint簽名的函數。 為了實現后者,我可以比較相關函數中的inspect.Signature對象,以hint我是否有辦法將類型提示轉換為簽名。

這是個有趣的問題。 我認為有兩種可能的方法,我推薦其中一種。

Callable構造Signature

這是可能的,但我不推薦這樣做。

問題在於Callable注釋僅包含Signature object 可以包含的信息的子集。 指定Callable類型的不可用信息包括:

  1. 參數名稱
  2. 參數種類(例如,僅位置或僅關鍵字等)
  3. 默認值

這意味着當你從一個類型構造一個Parameter時,你必須做出很多任意的選擇。

這是一個示例實現:

from collections.abc import Callable
from inspect import Parameter, Signature, signature
from typing import get_args


def callable_type_to_signature(callable_type: type) -> Signature:
    params, ret = get_args(callable_type)
    params = [
        Parameter(f"arg{i}", Parameter.POSITIONAL_ONLY, annotation=param)
        for i, param in enumerate(params)
    ]
    return Signature(params, return_annotation=ret)


def foo(x: int, y: str, z: dict[str, bool]) -> float:
    return NotImplemented


if __name__ == '__main__':
    hint_foo = Callable[[int, str, dict[str, bool]], float]
    sig = callable_type_to_signature(hint_foo)
    print(sig)
    print(signature(foo))

Output:

(arg0: int, arg1: str, arg2: dict[str, bool], /) -> float
(x: int, y: str, z: dict[str, bool]) -> float

請注意,我選擇將所有參數定義為僅位置參數,並為它們命名,如argX

仍然可以使用它來將一些 function 簽名與此callable_type_to_signature的 output 進行比較,但您必須注意不要將蘋果與橙子進行比較。

我認為有更好的方法。

比較SignatureCallable

由於您無論如何都想將簽名與類型提示進行比較,因此我認為您不需要創建另一個“假” Signature的額外步驟。 我們可以嘗試直接比較這兩個對象。 這是一個工作示例:

from collections.abc import Callable
from inspect import Parameter, Signature, signature
from typing import get_args, get_origin


def param_matches_type_hint(
    param: Parameter,
    type_hint: type,
    strict: bool = False,
) -> bool:
    """
    Returns `True` if the parameter annotation matches the type hint.

    For this to be the case:
    In `strict` mode, both must be exactly equal.
    If both are specified generic types, they must be exactly equal.
    If the parameter annotation is a specified generic type and
    the type hint is an unspecified generic type,
    the parameter type's origin must be that generic type.
    """
    param_origin = get_origin(param.annotation)
    type_hint_origin = get_origin(type_hint)
    if (
        strict or
        (param_origin is None and type_hint_origin is None) or
        (param_origin is not None and type_hint_origin is not None)
    ):
        return param.annotation == type_hint
    if param_origin is None and type_hint_origin is not None:
        return False
    return param_origin == type_hint


def signature_matches_type_hint(
    sig: Signature,
    type_hint: type,
    strict: bool = False,
) -> bool:
    """
    Returns `True` if the function signature and `Callable` type hint match.

    For details about parameter comparison, see `param_matches_type_hint`.
    """
    if get_origin(type_hint) != Callable:
        raise TypeError("type_hint must be a `Callable` type")
    type_params, return_type = get_args(type_hint)
    if sig.return_annotation != return_type:
        return False
    if len(sig.parameters) != len(type_params):
        return False
    return all(
        param_matches_type_hint(sig_param, type_param, strict=strict)
        for sig_param, type_param
        in zip(sig.parameters.values(), type_params)
    )


def foo(x: int, y: str, z: dict[str, bool]) -> float:
    return NotImplemented


def bar(x: dict[str, int]) -> bool:
    return NotImplemented


def baz(x: list) -> bool:
    return NotImplemented


if __name__ == '__main__':
    hint_foo = Callable[[int, str, dict], float]
    hint_bar = Callable[[dict], bool]
    hint_baz = Callable[[list[str]], bool]
    print(signature_matches_type_hint(signature(foo), hint_foo))
    print(signature_matches_type_hint(signature(bar), hint_bar))
    print(signature_matches_type_hint(signature(baz), hint_baz))
    print(signature_matches_type_hint(signature(bar), hint_bar, strict=True))

Output:

True
True
False
False

細節和注意事項

這是一個相當簡單的實現。 一方面,它不處理更多“異國情調”的簽名,例如包含任意關鍵字 arguments ( **kwargs ) 的簽名。 無論如何,還不完全清楚應該如何注釋。

這假設像baz這樣的更通用的 function 簽名與更具體的類型提示hint_baz兼容。 然而,反過來,像bar這樣的更具體function 簽名與更通用的類型提示hint_bar

如果你只想要關於類型的精確匹配,你可以使用strict=True

希望這對您有所幫助並使您走上正確的道路。 也許如果我有時間,我會嘗試擴展它並測試一下。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM