Python 运行时 Protocol:isinstance 已经通过,为什么方法参数和返回类型仍然不对
给插件入口写一个ByteReader协议,再用isinstance筛选对象,看起来像是已经验证了read(count)接口。实际却可能在调用时才发现少了一个参数,或者拿到整数而不是字节。runtime_checkable提供的是有限的运行时结构检查,类型标注不会因此变成完整的执行期合同。
完整代码已在CPython 3.12.14执行,只调用本文件中定义的内存对象。保存为demo.py,运行python3 demo.py。Protocol和runtime_checkable在Python 3.8加入;本文按3.12文档说明,动态生成属性的对象在不同版本上应另行验证。
AI模型生成概念图:检查门观察部件是否带有入口,部件内部是否兼容还需要另一步验证;图片不代表真实类型检查器界面。
先把两种错误分别造出来
GoodReader接收count并返回bytes;WrongArity没有count参数;WrongResult接收相同参数,却返回int。它们都没有继承ByteReader。这个安排可以把“有同名成员”“调用形式一致”和“返回值符合要求”分成三件可观察的事。
完整可运行程序
from typing import Protocol, runtime_checkable
@runtime_checkable
class ByteReader(Protocol):
def read(self, count: int) -> bytes:
...
class GoodReader:
def read(self, count: int) -> bytes:
return b"x" * count
class WrongArity:
def read(self) -> bytes:
return b"x"
class WrongResult:
def read(self, count: int) -> int:
return count
objects = [GoodReader(), WrongArity(), WrongResult(), object()]
checks = [isinstance(obj, ByteReader) for obj in objects]
assert checks == [True, True, True, False]
print("protocol checks:", checks)
assert objects[0].read(2) == b"xx"
print("good result:", objects[0].read(2))
try:
objects[1].read(2)
except TypeError:
print("wrong arity call: TypeError")
else:
raise AssertionError("wrong signature unexpectedly accepted")
result = objects[2].read(2)
assert isinstance(result, int)
print("wrong result type:", type(result).__name__)
def read_bytes(reader: ByteReader, count: int) -> bytes:
result = reader.read(count)
if not isinstance(result, bytes):
raise TypeError("read() must return bytes")
return result
assert read_bytes(GoodReader(), 2) == b"xx"
try:
read_bytes(WrongResult(), 2)
except TypeError as error:
print("explicit result check:", error)
else:
raise AssertionError("invalid result accepted")本次实际输出
protocol checks: [True, True, True, False] good result: b'xx' wrong arity call: TypeError wrong result type: int explicit result check: read() must return bytes
True只说明这轮结构检查通过
第一行是三个True和一个False,三个带read成员的对象都通过了,完全没有这个成员的object被拒绝。第二行b'xx'来自正常调用;第三行则说明通过结构检查的WrongArity仍在实际调用时抛出TypeError。
WrongResult的调用本身不会报错。它返回整数2,运行时也不会因为协议写着bytes就自动转换或拒绝。仅在入口打印一次isinstance为True,无法证明后面交给解码器的内容一定是字节。
把结果规则放到实际边界
read_bytes把成功执行后得到的对象检查为bytes,因而最后一行明确指出错误返回值。这个包装器只补上了本例真正关心的结果类型;它仍让调用期间的异常向上传播,没有用空字节掩盖失败。
如果业务还要求最多读取count个字节、count不得为负,或允许bytearray和memoryview,就应另外定义并测试那些条件。不要从一个bytes检查扩展出“读取器行为完全正确”的结论。静态类型检查也能提前发现示例中的不兼容实现,但本文没有运行任何第三方静态检查器,因此这里的结果只报告Python实跑。
协议适合表达能力,验收仍要覆盖行为
Protocol让无需共同父类的对象表达相同能力,适合库之间的结构化约定。runtime_checkable可用于粗粒度分支,却不应被当成调用任意插件前的安全认证。检查不会确认对象无副作用、不会阻塞,也不会给执行行为授权。
Python 3.12调整了运行时成员查找方式,并在协议类建立时固定用于检查的成员集合。如果项目依赖动态补属性或代理对象,应在真实解释器版本上增加样本。本例使用普通明确方法,刻意不把这些版本差异混进核心反例。
参考资料
资料核验日期:2026年10月2日。代码输出来自本文固定输入的本地实跑。


