Python 3.9 中 data Protocol 为何未装饰也能调用 isinstance?
数据小许
结论
需要用 isinstance() 在运行时检查 Protocol 时,应为协议添加 @runtime_checkable。Python 3.9 示例中的 AssertionError 表示 isinstance(B(), A) 返回了 False,检查没有通过。这个结果不能说明 data protocol 不需要装饰器。
为什么示例结果不同
A 声明了 x: int,因此 B 需要同时提供 x 和 f 才符合该协议。示例中的 B 只有 f,缺少 x。运行时结构检查返回 False 时,assert 就会抛出 AssertionError。
AssertionError 和 TypeError 表示的情况不同:前者表示表达式执行后得到假值;后者表示当前协议不允许用于这种运行时检查。给出的 Python 3.9 和 Python 3.10+ 输出表明,未装饰时的具体行为会因版本而异。仅凭这些输出无法确定 Python 3.9 内部为什么返回了假值,也不能把这种行为当作接口保证。
推荐写法
如果需要在运行时用 isinstance() 检查协议,请显式添加 @runtime_checkable,并确保实例实现协议要求的成员:
from typing import Protocol, runtime_checkable
@runtime_checkable
class A(Protocol):
x: int
def f(self): ...
class B:
x = 1
def f(self):
...
assert isinstance(B(), A)
运行时检查主要确认对象是否具备协议要求的成员。它不会验证成员的类型注解是否匹配,也不会检查方法签名是否符合静态类型要求。需要完整检查类型兼容性时,应使用静态类型检查工具。
注意事项
@runtime_checkable 允许对协议使用 isinstance(),但缺少成员的对象仍不会通过检查。对于包含数据成员的协议,也不要用 issubclass() 替代:类级别检查无法确认实例上是否存在数据属性。跨 Python 版本使用这类协议检查时,请显式装饰协议,并根据实际需要检查实例成员。
备注:内容仅供参考。
