繁体   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