跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Pythonjava

Python 的抽象基类、协议和鸭子类型

Python 里的接口抽象,核心是把“能做什么”从“属于谁”里拆出来。抽象基类(ABC)适合需要明确继承关系的场景,能把约束写死,但也会增加耦合;类型注解只是静态提示,不会在运行时拦住不匹配的对象。协议(Protocol)则更贴近鸭子类型,只关心对象是否实现了所需方法,配合 `@runtime_checkable` 还能在运行时做 `isinstance()` 判断。文章最后说明了 `Iterable` 这类协议背后的 `__subclasshook__` 机制,以及 ABC 和 Protocol 在“硬约束”和“软约束”上的实际取舍。

abccba发布于 2026/6/30更新于 2026/8/2216 浏览
Python 的抽象基类、协议和鸭子类型

面向接口编程与依赖倒置

'对接口编程,而不是对实现编程'这句话在 Python 里并不难理解,难的是怎么写得顺手。很多时候,你并不想把业务代码绑死在某个具体类上,只想表达一件事:只要它会做这件事,就够了。

先从一个简单的'人吃汉堡'例子开始。

抽象基类:更硬的约束

抽象基类(ABC)适合那些你就是想把边界画清楚的场景。它要求具体类继承某个基类,并实现规定的方法,这种约束比较硬,优点是清晰,代价是更强的耦合。

from abc import ABC, abstractmethod

class Burger(ABC):
    @abstractmethod
    def eat(self):
        pass

class ChickenBurger(Burger):
    def eat(self):
        print("啃鸡腿堡")

class Human:
    def eat_lunch(self, burger: Burger):
        burger.eat()

# 调用示例
Human().eat_lunch(ChickenBurger())

这里 Human 只依赖 Burger,不关心传进来的是鸡腿堡还是牛肉堡。后面你要换实现,只要新类继承 Burger,并把 eat 补上,Human 不用动。这就是依赖倒置真正省事的地方。

不过,ABC 也有它的毛病:它要求'出身'。有时你只是想让对象具备某个能力,不一定非得让它继承一棵家谱树。

类型注解不会替你拦住错误

Python 的类型注解更像提示,不是运行时的硬限制。解释器不会因为参数写了 Burger,就强制你传一个 Burger 的实例。

# 即使传入的不是显式继承 Burger 基类的实例,也可以成功运行
class Dumpling:
    def eat(self):
        print("吃饺子")

Human().eat_lunch(Dumpling()) # 运行时不会报错

这段代码能跑通,因为 Dumpling 也实现了 eat。如果你真要让 isinstance() 也认它,有两种路:要么显式继承,要么注册虚拟子类。

Burger.register(Dumpling)
print(isinstance(Dumpling(), Burger)) # True

但我个人不太喜欢把这套机制用得太重。Python 本来就更偏鸭子类型,平时看能力就够了,没必要总盯着血统不放。

协议:更像能力清单

Python 里的协议不是网络里的 HTTP 那种协议,而是一份行为约定:对象要扮演某个角色,就把对应的方法准备好。

这套思路比 ABC 更松一点,也更贴近 Python 的习惯。它关心的是对象'能不能做',不是'从哪来'。

协议和鸭子类型

如果一个对象有 __iter__,那它就能被当成可迭代对象;有 __enter__ 和 __exit__,它就能进 with。这类判断不看继承关系,只看行为。

下面这类结果经常会让刚接触的人愣一下:自定义类明明没显式继承 Iterable,isinstance() 却可能返回 True。原因很简单,Python 只是在检查它是不是具备那个角色需要的能力。

上下文管理器协议

上下文管理器是协议最直观的例子之一。数据库连接和'魔法传送门'当然没有任何继承关系,但只要它们都实现了 __enter__ 和 __exit__,就都能放进 with 里。

class DatabaseConnection:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

class MagicalPortal:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

with DatabaseConnection():
    pass

with MagicalPortal():
    pass

这类写法的好处很直接:调用方只认协议,不认具体实现。对上层代码来说,连接数据库和打开某个资源入口,最终都只是'进来、用完、退出'这三步。

typing.Protocol 和 @runtime_checkable

Protocol 是 Python 3.8 引入的静态协议工具。它的价值主要在类型检查阶段:IDE 能补全,mypy 也能检查出很多问题。缺点也很明显,它本身是为静态分析设计的,不是给运行时强校验准备的。

这意味着,直接拿 isinstance() 去测一个纯 Protocol,Python 会拒绝。

如果你确实希望协议既能做静态检查,又能在运行时参与 isinstance() 判断,就要加上 @runtime_checkable。

from typing import Protocol, runtime_checkable

@runtime_checkable
class Eatable(Protocol):
    def eat(self) -> None:
        ...

class Pizza:
    def eat(self): pass

print(isinstance(Pizza(), Eatable)) # True

加了这个装饰器后,协议就不只是类型提示了,它还能在运行时按'有没有这个能力'来判断对象。这种用法挺实用,但也别过度依赖;协议毕竟还是在帮助你表达约束,不是拿来替代所有校验逻辑的。

硬契约和软契约

ABC 和 Protocol 的区别,核心不在'谁更高级',而在'你想管到什么程度'。

  • ABC:要求继承关系明确,适合你想把实现边界锁死一点的场景。
  • Protocol:只看方法,不看父类,适合更松、更灵活的接口表达。

如果你在写框架、SDK 或者需要多人协作的公共抽象,ABC 能把事情说清楚;如果你在写业务代码,想少一点绑定、多一点可替换性,Protocol 往往更顺手。

__subclasshook__ 背后的判断

Iterable 这类协议之所以能'认'一个没继承它的类,靠的就是 __subclasshook__。

当你调用 isinstance(obj, SomeABC) 时,Python 大致会走这几步:先看有没有显式继承,再看有没有做过虚拟子类注册,最后才会问 __subclasshook__。只要这个钩子返回 True,即使前两条都不满足,结果也可以成立。

Iterable 的实现思路就是检查对象的类里有没有 __iter__。有,就认为它满足协议;没有,就不认。它不关心对象到底是谁生的,只关心它能不能迭代。

这套机制看起来像黑魔法,但其实和 Python 一贯的思路一致:少一点教条,多一点实际能力。

目录

  1. 面向接口编程与依赖倒置
  2. 抽象基类:更硬的约束
  3. 调用示例
  4. 类型注解不会替你拦住错误
  5. 即使传入的不是显式继承 Burger 基类的实例,也可以成功运行
  6. 协议:更像能力清单
  7. 协议和鸭子类型
  8. 上下文管理器协议
  9. typing.Protocol 和 @runtime_checkable
  10. 硬契约和软契约
  11. subclasshook 背后的判断
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • LLaMA-Factory 命令行工具 llamafactory-cli 核心指令实战
  • 无人机视觉语言导航入门:概念、挑战与应用
  • C++ 进阶:AVL 树的原理与实现
  • Rokid 灵珠平台搭建旅游 AR 智能体实战
  • Linux Mint 22.3 安装指南与系统优化实战
  • 网络安全 CTF 学习资源与常用解题工具整理
  • BAAI/bge-m3 WebUI 一键分析文本相似度
  • Agent 架构避坑指南:三层上下文裁剪模式与实践
  • Flutter 底部导航与 TabBar 多页切换实战及鸿蒙适配
  • DeepSeek 各版本说明与优缺点分析
  • Drupal 7 核心模块与常用扩展功能梳理
  • TrendRadar 本地部署指南:构建个人 AI 热点情报系统
  • Node.js 安装与环境配置实战指南
  • FPGA 开发环境搭建:Vivado 与 Vitis 2023.1 安装指南
  • LangChain 消息处理详解:缓存、过滤、合并与流式输出
  • 行星减速器:原理、计算公式与 C++ 实现
  • 使用 Python 查询和下载 Sentinel-1 轨道数据
  • Linux 匿名管道通信:原理与代码实战
  • Spring Data JPA 中 EntityManager 核心用法与持久化操作指南
  • IntelliJ IDEA 入门指南:从零创建并运行 Java 项目

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online