- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
8.6 弱引用
正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间。这经常用在缓存中。
弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。
弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。
示例 8-17 展示了如何使用 weakref.ref 实例获取所指对象。如果对象存在,调用弱引用可以获取对象;否则返回 None。
示例 8-17 是一个控制台会话,Python 控制台会自动把 _ 变量绑定到结果不为 None 的表达式结果上。这对我想演示的行为有影响,不过却凸显了一个实际问题:微观管理内存时,往往会得到意外的结果,因为不明显的隐式赋值会为对象创建新引用。控制台中的 _ 变量是一例。调用跟踪对象也常导致意料之外的引用。
示例 8-17 弱引用是可调用的对象,返回的是被引用的对象;如果所指对象不存在了,返回 None
>>> import weakref >>> a_set = {0, 1} >>> wref = weakref.ref(a_set) ➊ >>> wref <weakref at 0x100637598; to 'set' at 0x100636748> >>> wref() ➋ {0, 1} >>> a_set = {2, 3, 4} ➌ >>> wref() ➍ {0, 1} >>> wref() is None ➎ False >>> wref() is None ➏ True
❶ 创建弱引用对象 wref,下一行审查它。
❷ 调用 wref() 返回的是被引用的对象,{0, 1}。因为这是控制台会话,所以 {0, 1} 会绑定给 _ 变量。
❸ a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _ 变量仍然指代它。
❹ 调用 wref() 依旧返回 {0, 1}。
❺ 计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。但是,随后 _ 绑定到结果值 False。现在 {0, 1} 没有强引用了。
❻ 因为 {0, 1} 对象不存在了,所以 wref() 返回 None。
weakref 模块的文档指出,weakref.ref 类其实是低层接口,供高级用途使用,多数程序最好使用 weakref 集合和 finalize。也就是说,应该使用 WeakKeyDictionary、WeakValueDictionary、WeakSet 和 finalize(在内部使用弱引用),不要自己动手创建并处理 weakref.ref 实例。我们在示例 8-17 中那么做是希望借助实际使用 weakref.ref 来褪去它的神秘色彩。但是实际上,多数时候 Python 程序都使用 weakref 集合。
下一节简要讨论 weakref 集合。
8.6.1 WeakValueDictionary简介
WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从 WeakValueDictionary 中删除。因此,WeakValueDictionary 经常用于缓存。
我们对 WeakValueDictionary 的演示受到来自英国六人喜剧团体 Monty Python 的经典短剧《奶酪店》的启发,在那出短剧里,客户问了 40 多种奶酪,包括切达干酪和马苏里拉奶酪,但是都没有货。3
3cheeseshop.python.org 还是 PyPI(Python Package Index 软件仓库)的别名,一开始里面什么也没有。写作本书时,Python Cheese Shop 中有 41 426 个包。还不错,但是与有 131 000 个模块的 CPAN(Comprehensive Perl Archive Network)相比,还差得远。所有动态语言社区都羡慕 CPAN 中有那么多模块。
示例 8-18 实现一个简单的类,表示各种奶酪。
示例 8-18 Cheese 有个 kind 属性和标准的字符串表示形式
class Cheese: def __init__(self, kind): self.kind = kind def __repr__(self): return 'Cheese(%r)' % self.kind
在示例 8-19 中,我们把 catalog 中的各种奶酪载入 WeakValueDictionary 实现的 stock 中。然而,删除 catalog 后,stock 中只剩下一种奶酪了。你知道为什么帕尔马干酪(Parmesan)比其他奶酪保存的时间长吗? 4 代码后面的提示中有答案。
4帕尔马干酪在工厂中至少要存储一年,因此它比其他新鲜奶酪的保存时间长。但是,这不是我们想要的答案。
示例 8-19 顾客:“你们店里到底有没有奶酪?”
>>> import weakref >>> stock = weakref.WeakValueDictionary() ➊ >>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'), ... Cheese('Brie'), Cheese('Parmesan')] ... >>> for cheese in catalog: ... stock[cheese.kind] = cheese ➋ ... >>> sorted(stock.keys()) ['Brie', 'Parmesan', 'Red Leicester', 'Tilsit'] ➌ >>> del catalog >>> sorted(stock.keys()) ['Parmesan'] ➍ >>> del cheese >>> sorted(stock.keys()) []
❶ stock 是 WeakValueDictionary 实例。
❷ stock 把奶酪的名称映射到 catalog 中 Cheese 实例的弱引用上。
❸ stock 是完整的。
❹ 删除 catalog 之后,stock 中的大多数奶酪都不见了,这是 WeakValueDictionary 的预期行为。为什么不是全部呢?
临时变量引用了对象,这可能会导致该变量的存在时间比预期长。通常,这对局部变量来说不是问题,因为它们在函数返回时会被销毁。但是在示例 8-19 中,for 循环中的变量 cheese 是全局变量,除非显式删除,否则不会消失。
与 WeakValueDictionary 对应的是 WeakKeyDictionary,后者的键是弱引用。weakref.WeakKeyDictionary 的文档指出了一些可能的用途:
(WeakKeyDictionary 实例)可以为应用中其他部分拥有的对象附加数据,这样就无需为对象添加属性。这对覆盖属性访问权限的对象尤其有用。
weakref 模块还提供了 WeakSet 类,按照文档的说明,这个类的作用很简单:“保存元素弱引用的集合类。元素没有强引用时,集合会把它删除。”如果一个类需要知道所有实例,一种好的方案是创建一个 WeakSet 类型的类属性,保存实例的引用。如果使用常规的 set,实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与 Python 进程一样长,除非显式删除类。
这些集合,以及一般的弱引用,能处理的对象类型有限。参见下一节的说明。
8.6.2 弱引用的局限
不是每个 Python 对象都可以作为弱引用的目标(或称所指对象)。基本的 list 和 dict 实例不能作为所指对象,但是它们的子类可以轻松地解决这个问题:
class MyList(list): """list的子类,实例可以作为弱引用的目标""" a_list = MyList(range(10)) # a_list可以作为弱引用的目标 wref_to_a_list = weakref.ref(a_list)
set 实例可以作为所指对象,因此实例 8-17 才使用 set 实例。用户定义的类型也没问题,这就解释了示例 8-19 中为什么使用那个简单的 Cheese 类。但是,int 和 tuple 实例不能作为弱引用的目标,甚至它们的子类也不行。
这些局限基本上是 CPython 的实现细节,在其他 Python 解释器中情况可能不一样。这些局限是内部优化导致的结果,下一节会以其中几个类型为例讨论(完全选读)。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论