Python Protocol 与结构化类型

Protocol 在类型检查层表达结构要求,实现静态鸭子类型,与运行时鸭子类型互补。

#type / concept #status / growing #tech / dev #resource / python #tech / lang / python

[!info] 关联笔记

Python Protocol 与结构化类型

这个概念为什么出现

运行时鸭子类型灵活,但公共库希望在 CI 就发现“少了 close 方法”。typing.Protocol 让类型检查器按结构判定兼容,无需名义继承。

[!abstract] 一句话理解 Protocol 定义一组必须具备的方法/属性;类型检查器对结构匹配的类型放行,即使它们未显式继承该 Protocol。

最小可运行示例

先把示例放进业务场景,再看代码:

场景:关闭资源的统一收尾函数

# protocol_closer.py
# 业务意图:close_all 接受任何带 close() 的对象。
# 教学点:
# - Protocol 定义结构;
# - 不同类结构兼容;
# - 运行时不必继承 Protocol(除非 runtime_checkable)。

from typing import Protocol


class Closable(Protocol):
    def close(self) -> None: ...


class DbConn:
    def close(self) -> None:
        print("db closed")


class FileHandle:
    def close(self) -> None:
        print("file closed")


def close_all(resources: list[Closable]) -> None:
    for r in resources:
        r.close()


def main() -> None:
    close_all([DbConn(), FileHandle()])


if __name__ == "__main__":
    main()

建议运行:

python protocol_closer.py

期望输出:

db closed
file closed

结合场景再看三个关注点

  1. DbConn/FileHandle 无共同基类仍可协作。
  2. 静态检查器会拒绝缺 close 的类型传入。
  3. 需要运行时 isinstance 再用 @runtime_checkable(有限制)。

核心概念与准确模型

  • 结构子类型 vs 名义子类型
  • Protocol 成员用 ... 占位
  • 可泛型 Protocol
  • 与 ABC 虚拟子类不同层

边界情况与反直觉行为

  1. 仅属性/方法集合匹配——语义仍需文档。
  2. runtime_checkable 只查存在性,不查签名细节。
  3. 回调协议Callable 有时更简单。

常见误区

[!warning] 常见误区:Protocol 会改变运行时继承 错误理解:必须 subclass Protocol。
正确模型:默认是静态契约;运行时对象无需知道 Protocol。

工程实践

  • 公共插件点用 Protocol 文档化
  • 与 pyright 严格模式配合
  • 小而稳的协议优于大而全

本节总结

Protocol 把鸭子类型提升到可检查的工程实践,不牺牲组合灵活性。

自测题

  1. Protocol 与 ABC 强制继承差别?
  2. 为什么还要测试?
参考答案
  1. Protocol 结构兼容即可;ABC 常要名义注册/继承。
  2. 结构匹配不等于业务语义正确。

延伸阅读与资料来源

资料类型支撑内容
Protocol文档API
PEP 544PEPProtocols
创建于 2026/7/15 更新于 2026/7/15