简体   繁体   English

Python inspect.Signature from typing.Callable

[英]Python inspect.Signature from typing.Callable

How can I convert a typing.Callable type hint into a inspect.Signature function signature object?如何将typing.Callable类型提示转换为inspect.Signature function signature object?

Use case用例

Let's say I have a custom type hint for a function signature hint :假设我有一个 function 签名hint

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

I want to use it both for type hinting as well as finding functions which conform to the signature of hint .我想将它用于类型提示以及查找符合hint签名的函数。 To achieve the latter, I could compare inspect.Signature objects from the functions in question to hint if I had a way of converting the type hint into a signature.为了实现后者,我可以比较相关函数中的inspect.Signature对象,以hint我是否有办法将类型提示转换为签名。

This is an interesting question.这是个有趣的问题。 I think there are two possible approaches, one of which I would recommend.我认为有两种可能的方法,我推荐其中一种。

Construct Signature from CallableCallable构造Signature

This is possible, but I would not recommend this.这是可能的,但我不推荐这样做。

The problem is that the Callable annotation contains only a subset of the information that a Signature object can hold.问题在于Callable注释仅包含Signature object 可以包含的信息的子集。 Information not available on a specified Callable type includes:指定Callable类型的不可用信息包括:

  1. Parameter names参数名称
  2. Parameter kinds (eg positional only or keyword-only etc.)参数种类(例如,仅位置或仅关键字等)
  3. Default values默认值

This means you have to make a lot of arbitrary choices, when you construct a Parameter from a type.这意味着当你从一个类型构造一个Parameter时,你必须做出很多任意的选择。

Here is an example implementation:这是一个示例实现:

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: Output:

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

Notice that I chose to define all parameters as positional only and give them all names like argX .请注意,我选择将所有参数定义为仅位置参数,并为它们命名,如argX

You could still use this to compare some function signatures with the output of this callable_type_to_signature , but you would have to take care to not compare apples to oranges.仍然可以使用它来将一些 function 签名与此callable_type_to_signature的 output 进行比较,但您必须注意不要将苹果与橙子进行比较。

I think there is a better way.我认为有更好的方法。

Compare Signature to Callable比较SignatureCallable

Since you wanted to compare signatures to type hints anyway, I think you don't need that extra step of creating another "fake" Signature .由于您无论如何都想将签名与类型提示进行比较,因此我认为您不需要创建另一个“假” Signature的额外步骤。 We can try and compare the two objects directly.我们可以尝试直接比较这两个对象。 Here is a working example:这是一个工作示例:

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: Output:

True
True
False
False

Details and caveats :细节和注意事项

This is a rather simplistic implementation.这是一个相当简单的实现。 For one thing, it doesn't handle more "exotic" signatures, such as those that contain arbitrary keyword arguments ( **kwargs ).一方面,它不处理更多“异国情调”的签名,例如包含任意关键字 arguments ( **kwargs ) 的签名。 It is not entirely clear how that should be annotated anyway.无论如何,还不完全清楚应该如何注释。

This assumes that a more general function signature like that of baz is not compatible with the more specific type hint hint_baz .这假设像baz这样的更通用的 function 签名与更具体的类型提示hint_baz兼容。 The other way around however, the more specific function signature like that of bar is compatible with the more general type hint hint_bar .然而,反过来,像bar这样的更具体function 签名与更通用的类型提示hint_bar

If you only want exact matches regarding types, you can use strict=True .如果你只想要关于类型的精确匹配,你可以使用strict=True

Hope this helps a bit and puts you on the right track.希望这对您有所帮助并使您走上正确的道路。 Maybe if I find the time, I'll try to extend this and test it a bit.也许如果我有时间,我会尝试扩展它并测试一下。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 python 带有 kwargs 的 function 的打字签名(typing.Callable) - python typing signature (typing.Callable) for function with kwargs 如何使用 Python @singledispatch 注册 Typing.Callable? - How to register typing.Callable with Python @singledispatch? python typing.Callable 到底是什么? - what exactly is python typing.Callable? 使用 PEP 563 检查签名 - inspect.signature with PEP 563 Python 3.6 inspect.signature()不显示参数 - Python 3.6 inspect.signature() doesn't show arguments Python的inspect.Signature的签名似乎违反了python的签名规则 - Python's inspect.Signature's signature seems to violate python's signatures rules 如何以与 inspect.signature 一起使用的方式包装 python 函数? - How can I wrap a python function in a way that works with with inspect.signature? 如何在 Python 中使用辅助函数作为 typing.Callable 参数和 playwright page.wait_for_event 函数? - How do I use a helper function as typing.Callable argument with the playwright page.wait_for_event function in Python? inspect.signature究竟是如何与类一起工作的? - How exactly does inspect.signature work with classes? 使用类型提示注释使用 types.FunctionType 还是 typing.Callable? - Using type hint annotation using types.FunctionType vs typing.Callable?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM