当前位置: 首页 > news >正文

Python 接口:从协议到抽象基 类(使用猴子补丁在运行时实现协议)

使用猴子补丁在运行时实现协议

示例 11-4 中的 FrenchDeck 类有个重大缺陷:无法洗牌。几年前,第
一次编写 FrenchDeck 示例时,我实现了 shuffle 方法。后来,我对
Python 风格有了深刻理解,我发现如果 FrenchDeck 实例的行为像序
列,那么它就不需要 shuffle 方法,因为已经有 random.shuffle 函
数可用,文档中说它的作用是“就地打乱序列
x”(https://docs.python.org/3/library/random.html#random.shuffle)。

如果遵守既定协议,很有可能增加利用现有的标准库和第三
方代码的可能性,这得益于鸭子类型。

标准库中的 random.shuffle 函数用法如下:

>>> from random import shuffle
>>> l = list(range(10))
>>> shuffle(l)
>>> l
[5, 2, 9, 7, 8, 3, 1, 4, 0, 6]

然而,如果尝试打乱 FrenchDeck 实例,会出现异常,如示例 11-5 所
示。

示例 11-5 random.shuffle 函数不能打乱 FrenchDeck 实例

>>> from random import shuffle
>>> from frenchdeck import FrenchDeck
>>> deck = FrenchDeck()
>>> shuffle(deck)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".../python3.3/random.py", line 265, in shuffle
x[i], x[j] = x[j], x[i]
TypeError: 'FrenchDeck' object does not support item assignment

错误消息相当明确,“‘FrenchDeck’ object does not support item
assignment”(‘FrenchDeck’ 对象不支持为元素赋值)。这个问题的原因是,shuffle 函数要调换集合中元素的位置,而 FrenchDeck 只实现
了不可变的序列协议。可变的序列还必须提供__setitem__ 方法。

Python 是动态语言,因此我们可以在运行时修正这个问题,甚至还可以
在交互式控制台中,修正方法如示例 11-6 所示。

示例 11-6 为FrenchDeck 打猴子补丁,把它变成可变的,让
random.shuffle 函数能处理(接续示例 11-5)

>>> def set_card(deck, position, card):... deck._cards[position] = card
...
>>> FrenchDeck.__setitem__ = set_card ➋
>>> shuffle(deck)>>> deck[:5]
[Card(rank='3', suit='hearts'), Card(rank='4', suit='diamonds'), Card(rank='4',
suit='clubs'), Card(rank='7', suit='hearts'), Card(rank='9', suit='spades')]

❶ 定义一个函数,它的参数为 deck、position 和 card。
❷ 把那个函数赋值给 FrenchDeck 类的__setitem__ 属性。
❸ 现在可以打乱 deck 了,因为 FrenchDeck 实现了可变序列协议所需
的方法。
特殊方法__setitem__ 的签名在 Python 语言参考手册的“3.3.6.
Emulating container
types”(https://docs.python.org/3/reference/datamodel.html#emulatingcontainer-
types)中定义。语言参考中使用的参数是 self、key 和
value,而这里使用的是 deck、position 和 card。这么做是为了告
诉你,每个 Python 方法说到底都是普通函数,把第一个参数命名为
self 只是一种约定。在控制台会话中使用那几个参数没问题,不过在
Python 源码文件中最好按照文档那样使用 self、key 和 value。
这里的关键是,set_card 函数要知道 deck 对象有一个名为 cards 的
属性,而且 cards 的值必须是可变序列。然后,我们把 set_card 函
数赋值给特殊方法__setitem
,从而把它依附到 FrenchDeck 类
上。这种技术叫猴子补丁:在运行时修改类或模块,而不改动源码。猴
子补丁很强大,但是打补丁的代码与要打补丁的程序耦合十分紧密,而
且往往要处理隐藏和没有文档的部分。
除了举例说明猴子补丁之外,示例 11-6 还强调了协议是动态
的:random.shuffle 函数不关心参数的类型,只要那个对象实现了部
分可变序列协议即可。即便对象一开始没有所需的方法也没关系,后来
再提供也行。
目前,本章讨论的主题是“鸭子类型”:对象的类型无关紧要,只要实现
了特定的协议即可。
前面给出的抽象基类图表是为了展示协议与抽象基类的文档中所说的接
口之间的关系,但是目前为止还没有真正继承抽象基类。
在接下来的几节中,我们将直接使用抽象基类,而不只将其当作文档。

http://www.xdnf.cn/news/787951.html

相关文章:

  • LangChain实战:文档加载、分割与向量存储详解
  • 第三十三天打卡复习
  • 机器学习——放回抽样
  • 008房屋租赁系统技术揭秘:构建智能租赁服务生态
  • Matlab自学笔记五十七:符号运算、可变精度运算、双精度浮点型运算,三种运算精度的概念、比较、选择和应用
  • 【25-cv-05991】Keith律所代理Ana Maria油画版权
  • kubeSphere安装使用
  • 流、线程、任务、队列等相关在不同语言的区别联系
  • 项目计划缺乏风险评估和应对策略,如何完善
  • Qiskit:量子计算模拟器
  • Prj09--8088单板机C语言8253产生1KHz方波(1)
  • HTTP Error 400 Bad request 问题分析解决
  • spring boot应答500问题跟踪
  • YAML 文件中不同格式的含义详解
  • Flink 重启后事件被重复消费的原因与解决方案
  • Deep Search之R1-Searcher系列
  • QT实现动画翻转效果
  • Docker 镜像深度剖析:构建、管理与优化
  • 多模态知识图谱可视化构建(neo4j+python+flask+vue环境搭建与示例)
  • 秋招准备-数据结构
  • 前端面试题之Class详解
  • @Resource和@Autowire
  • 《前端面试题:CSS预处理器(Sass、Less等)》
  • 代码训练LeetCode(19)轮转数组
  • 【学习记录】深入解析 AI 交互中的五大核心概念:Prompt、Agent、MCP、Function Calling 与 Tools
  • 全球常用地理信息、遥感数据处理软件介绍(单机版、在线云平台)
  • LeetCode 高频 SQL 50 题(基础版) 之 【高级查询和连接】· 下
  • 【Typst】5.文档结构元素与函数
  • 突破视觉认知边界VisionReasoner:用强化学习统一视觉感知与推理的全能框架
  • 防火墙在OSI模型中的层级工作(2025)