Python 如何用 typing.Protocol 定义“鸭子类型”接口
技术百科
舞夢輝影
发布时间:2026-01-23
浏览: 次 typing.Protocol 是结构类型检查机制,仅在静态类型检查时生效,不生成运行时对象,也不参与 isinstance 或 issubclass 判断;需用 @runtime_checkable 才支持运行时结构校验。
typing.Protocol 本质是结构类型检查,不是运行时接口
Python 的 Protocol 不会生成运行时对象,也不参与 isinstance 或 issubclass 判断——它只在类型检查器(如 mypy、pyright)中生效。如果你期望用 isinstance(obj, MyProtocol) 来做运行时鸭子类型校验,会直接报错:TypeError: isinstance() argument 2 cannot be a parameterized generic。
常见误用场景:把 Protocol 当成抽象基类(ABC)来用,结果运行时报错或类型检查不触发。关键区别在于:Protocol 关注“有没有这些方法”,ABC 关注“是不是这个类型”。
- 要运行时检查鸭子类型,得自己写
hasattr+callable组合,或用collections.abc中的现成 ABC(如Iterable) - 要让 mypy 报错,必须开启类型检查(例如命令行加
--strict),否则Protocol形同虚设 - 定义带泛型的
Protocol时,必须继承Generic[T],且不能漏掉__parameters__的隐式推导逻辑
定义最小可行 Protocol:只声明方法签名,不写实现
Protocol 类里只允许出现方法签名、属性注解、类变量(带默认值的常量),禁止任何方法体、self 参数的具体类型标注(除非是泛型绑定)、__init__ 实现等。否则 mypy 会警告 Protocol cannot have non-method members。
例如想约束“能被 len() 调用且支持索引”的对象:
from typing import Protocolclass SizedIndexable(Protocol): def len(self) -> int: ... def getitem(self, i: int) -> str: ...
注意三点:
- 方法末尾必须加
...(省略号),这是语法强制要求,表示无实现 - 返回类型必须明确,不能留空或写
pass - 如果方法接受泛型参数(如
T),需在类头声明class MyProto(Protocol, Generic[T]):
Protocol 和 ABC 混用:用 @runtime_checkable 让 isinstance 生效
标准库的 typing.runtime_checkable 装饰器可以让某个 Protocol 支持运行时 isinstance 检查,但仅限于“结构匹配”——即对象动态拥有所有声明的方法/属性,且它们是可调用的。
示例:
from typing import Protocol, runtime_checkable@runtime_checkable class Readable(Protocol): def read(self, size: int = -1) -> bytes: ...
class FakeFile: def read(self, size: int = -1) -> bytes: return b"hello"
assert isinstance(FakeFile(), Readable) # ✅ 成立
但要注意:
-
@runtime_checkable不检查方法签名是否一致(比如参数名、默认值、返回类型),只检查存在性和可调用性 - 若对象的
read方法实际接收**kwargs,isinstance仍返回True,但类型检查器可能报错 - 装饰后无法再继承其他
Protocol(mypy 限制),多继承需谨慎
Protocol 常见陷阱:__init__、属性赋值、可变默认参数
很多人试图在 Protocol 中定义 __init__ 来约束构造方式,这是无效的——Protocol 不控制实例化过程。同样,给属性赋值(如 name: str = "default")会被视为实例变量,而非类型契约的一部分,容易误导。
真正该关注的是“调用后行为”,比如:
- 需要对象有
.name属性并可读?写name: str(只注解,不赋值) - 需要对象有
.name并可写?得额外声明name: str
和
@name.setter方法签名 - 需要兼容
dataclass或NamedTuple?确保字段名和类型完全一致,否则 mypy 可能不认为其满足协议 - 用了可变默认参数(如
def append(self, items: list = []))?类型检查器会按字面理解,但运行时风险得靠代码规范规避
最易被忽略的一点:Protocol 的成员访问是“宽泛匹配”。哪怕一个类只实现了协议中 1 个方法,mypy 默认也不会报错——除非你显式标注变量类型为该 Protocol,并尝试调用未实现的方法。
# 的是
# 这是
# 也不
# 很多人
# 如果你
# 用了
# python
# 为其
# app
# default
# 对象
# class
# 泛型
# 区别
# 标准库
# 子类
# 接口
# 报错
# 继承
# len
# 默认值
# Generic
# 常量
# append
# 变量类型
# 多继承
# 形同虚设
# 代码规范
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Win11怎么打开旧版计算器_Win11恢复传统计
- Win11如何设置计划任务 Win11定时执行程序
- Python大文件处理策略_内存优化说明【指导】
- Win11讲述人怎么关闭_Win11误触开启语音朗
- 如何将竖排文本文件转换为横排字符串
- Win11怎么设置多显示器任务栏 Win11扩展任
- PHP cURL GET请求:正确设置请求头与身份
- Windows11怎样开启游戏模式_Windows
- 如何使用Golang优化模块引入路径_Golang
- 如何在Golang中修改数组元素_通过指针实现原地
- php8.4xdebug无法调试怎么办_php8.
- 如何在 Go 应用中实现自动错误恢复与进程重启机制
- c++怎么编写动态链接库dll_c++ __dec
- Win11怎么关闭专注助手 Win11关闭免打扰模
- Win11怎么开启剪贴板历史记录_Windows1
- php怎么下载安装后设置默认字符集_utf8配置步
- 如何用列表一次性对 DataFrame 的指定列应
- Win11怎么开启智能存储_Windows11存储
- Win11怎么把图标拖到任务栏_Win11固定应用
- Django 测试数据库表缺失与字段未创建问题的完
- Python与MongoDB NoSQL开发实战_
- C++ static_cast和dynamic_c
- Windows怎样关闭桌面弹窗广告_Windows
- C#如何在一个XML文件中查找并替换文本内容
- php接口返回数据乱码怎么办_php接口调试编码问
- Windows10如何更改鼠标图标_Win10鼠标
- php条件判断怎么写_ifelse和switchc
- Win11如何卸载OneDrive_Win11卸载
- Windows10蓝屏SYSTEM_SERVICE
- Go 中 defer 在 goroutine 内部
- C++中的std::shared_from_thi
- 如何在Golang中使用log包输出不同级别日志_
- Python爬虫项目实战教程_Scrapy抓取与存
- 如何在 ACF 中正确更新嵌套多层 Group 字
- Windows 11怎么设置默认解压软件_Wind
- php中作用域操作符能访问私有静态属性吗_访问权限
- Win10闹钟铃声怎么自定义 Win10闹钟自定义
- Win11怎么更改鼠标指针_Windows 11自
- 如何在Mac上搭建Golang开发环境_使用Hom
- Win11怎么关闭防火墙通知_屏蔽Win11安全中
- Windows10电脑怎么设置虚拟内存_Win10
- php下载安装包怎么选_threadsafe与nt
- MAC怎么截图并快速编辑_MAC自带截图快捷键与标
- PHP的Workerman对架构扩展有啥帮助_应用
- Go 语言标准库为何不提供泛型 Contains
- Go 中实现 Python urllib.quot
- php订单日志怎么按金额排序_php按订单金额排序
- 如何在 Go 中比较自定义的数组类型(如 [20]
- Win11系统占用空间大怎么办 Win11深度瘦身
- MAC的“接续互通”功能无法使用怎么办_MAC检查


QQ客服