如何使 "isinstance" on Protocols 还包括函数签名和数据类型?

How can I make "isinstance" on Protocols also include function signatures and data-types?

以下代码定义了一个简单的协议和一个 class,almost 实现了该协议。唯一的区别是 run() 方法在协议中接受一个参数,但它不是以这种方式实现的。

然而,isinstance() 检查 returns 真,这是出乎意料的。

据我了解PEP-544,此应该 有效。虽然关于检查函数签名还很不清楚。

ORDER 成员中使用错误的数据类型时会发生同样的问题(f.ex。在协议中将其更改为 str)。

我知道类型提示只是……好吧……“提示”,不会在运行时强制执行。

但是,在我的应用程序中,确保某些 classes 遵循定义的协议以获得更清晰的错误消息会很有用。它使用的是插件架构,在加载插件后,如果该插件遵循所需的协议,则进行快速“健全性检查”将很有用,如果不遵循,则尽早提供有用的错误消息而不是导致异常稍后在下游。

from typing_extensions import Protocol, runtime_checkable


@runtime_checkable
class PFoo(Protocol):
    ORDER: int

    def run(self, a: int) -> None:
        ...


class Hello:
    ORDER = 10

    def run(self) -> None:
        print(1)


# Returns "True" evn though the signature of "run()" doesn't match
print(isinstance(Hello(), PFoo))

这是 typing moduleruntime_checkable 函数的文档字符串:

"""Mark a protocol class as a runtime protocol.

Such protocol can be used with isinstance() and issubclass().
Raise TypeError if applied to a non-protocol class.
This allows a simple-minded structural check very similar to
one trick ponies in collections.abc such as Iterable.
For example::

    @runtime_checkable
    class Closable(Protocol):
        def close(self): ...

    assert isinstance(open('/some/file'), Closable)

Warning: this will check only the presence of the required methods,
not their type signatures!
"""

如果你想比较函数的类型注释,你可以检查函数的 __annotations__ 属性(如果它是一个启用了 from __future__ import annotations 的模块,最好使用 typing.get_type_hints on the function rather than examining the __annotations__ attribute directly). And if you're looking for an exact signature match, you can use the signature function from the inspect module — docs here。注意:我认为使用 inspect.signature 可能会产生一些运行时性能成本(但话又说回来,所有运行时类型检查也是如此)。