- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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.9 延伸阅读
Python 语言参考手册中“Data Model”一章的开头清楚解释了对象的标识和值。
“Python 核心系列”图书的作者 Wesley Chun 在 OSCON 2013 做了一场精彩的演讲,涵盖了本章讨论的很多话题。在“Python 103: Memory Model & Best Practices”演讲页面可以下载幻灯片。Wesley 在 EuroPython 2011 还做过一次更长的演讲(YouTube 视频),不仅涵盖了本章的主题,还讨论了特殊方法的使用。
Doug Hellmann 写了一长串精彩的博客文章,题为“Python Module of the Week”,8 后来集结成书,即《Python 标准库》。他写的“copy - Duplicate Objects”9 和“weakref - Garbage-Collectable References to Objects”10 两篇文章涵盖了本章讨论的部分话题。
8原来是基于 Python 2 的,现在已经改为基于 Python 3。——编者注
9新的版本基于 Python 3。——编者注
10新的版本基于 Python 3,并改名为“weakref - Impermanent References to Objects”。——编者注
关于 CPython 分代垃圾回收程序的更多信息,请参阅 gc 模块的文档。文档开头的第一句话是:“这个模块为可选的垃圾回收程序提供接口。”“可选的”这个修饰词可能让人惊讶,不过“Data Model”一章也说:
垃圾回收可以延缓实现,或者完全不实现——如何实现垃圾回收是实现的质量问题,只要不把还能获得的对象给回收了就行。
Fredrik Lundh(很多核心库的创建者,如 ElementTree、Tkinter 和图像库 PIL)写了一篇短文,谈论了 Python 的垃圾回收程序,题为“How Does Python Manage Memory?”。他强调垃圾回收程序是一种实现的特性,其行为在不同的 Python 解释器中有所不同。例如,Jython 用的是 Java 垃圾回收程序。
CPython 3.4 改进了处理有 __del__ 方法的对象的方式,参见“PEP 442—Safe object finalization”。
维基百科中有一篇文章讲解了字符串驻留,那篇文章提到了几种语言对这个技术的利用,包括 Python。
杂谈
平等对待所有对象
发现 Python 之前,我学过Java。我一直觉得 Java 的 == 运算符用着不舒服。程序员关注的基本上是相等性,而不是标识,但是 Java 的 == 运算符比较的是对象(不是基本类型)的引用,而不是对象的值。就算是比较字符串这样的基本操作,Java 也强制你使用 .equals 方法。尽管如此,.equals 方法还有另一个问题:如果编写 a.equals(b),而 a 是 null,会得到一个空指针异常。Java 设计者觉得有必要重载字符串的 + 运算符,那为什么不把 == 也重载了?
Python 采取了正确的方式。== 运算符比较对象的值,而 is 比较引用。此外,Python 支持重载运算符,== 能正确处理标准库中的所有对象,包括 None——这是一个正常的对象,与 Java 的 null 不同。
当然,你可以在自己的类中定义 __eq__ 方法,决定 == 如何比较实例。如果不覆盖 __eq__ 方法,那么从 object 继承的方法比较对象的 ID,因此这种后备机制认为用户定义的类的各个实例是不同的。
1998 年 9 月的一个下午,读完 Python 教程后,考虑到这些行为,我立即就从 Java 转到 Python 了。
可变性
如果所有 Python 对象都是不可变的,那么本章就没有存在的必要了。处理不可变的对象时,变量保存的是真正的对象还是共享对象的引用无关紧要。如果 a == b 成立,而且两个对象都不会变,那么它们就可能是相同的对象。这就是为什么字符串可以安全使用驻留。仅当对象可变时,对象标识才重要。
在“纯”函数式编程中,所有数据都是不可变的,如果为集合追加元素,那么其实会创建新的集合。然而,Python 不是函数式语言,更别提纯不纯了。在 Python 中,用户定义的类,其实例默认可变(多数面向对象语言都是如此)。自己创建对象时,如果需要不可变的对象,一定要格外小心。此时,对象的每个属性都必须是不可变的,否则会出现类似元组那种行为:元组本身不可变,但是如果里面保存着可变对象,那么元组的值可能会变。
可变对象还是导致多线程编程难以处理的主要原因,因为某个线程改动对象后,如果不正确地同步,那就会损坏数据。但是过度同步又会导致死锁。
对象析构和垃圾回收
Python 没有直接销毁对象的机制,这一疏漏其实是一个好的特性:如果随时可以销毁对象,那么指向对象的强引用怎么办?
CPython 中的垃圾回收主要依靠引用计数,这容易实现,但是遇到引用循环容易泄露内存,因此 CPython 2.0(2000 年 10 月发布)实现了分代垃圾回收程序,它能把引用循环中不可获取的对象销毁。
但是引用计数仍然作为一种基准存在,一旦引用数量归零,就立即销毁对象。这意味着,在 CPython 中,这样写是安全的(至少目前如此):
open('test.txt', 'wt', encoding='utf-8').write('1, 2, 3')
这行代码是安全的,因为文件对象的引用数量会在 write 方法返回后归零,Python 在销毁内存中表示文件的对象之前,会立即关闭文件。然而,这行代码在 Jython 或 IronPython 中却不安全,因为它们使用的是宿主运行时(Java VM 和 .NET CLR)中的垃圾回收程序,那些回收程序更复杂,但是不依靠引用计数,而且销毁对象和关闭文件的时间可能更长。在任何情况下,包括 CPython,最好显式关闭文件;而关闭文件的最可靠方式是使用 with 语句,它能保证文件一定会被关闭,即使打开文件时抛出了异常也无妨。使用 with,上述代码片段变成了:
with open('test.txt', 'wt', encoding='utf-8') as fp: fp.write('1, 2, 3')
如果对垃圾回收程序感兴趣,可以阅读 Thomas Perl 的论文,“Python Garbage Collector Implementations: CPython, PyPy and GaS”。就是从那篇论文中,我得知在 CPython 中 open().write() 是安全的。
参数传递:共享传参
解释 Python 中参数传递的方式时,人们经常这样说:“参数按值传递,但是这里的值是引用。”这么说没错,但是会引起误解,因为在旧式语言中,最常用的参数传递模式有按值传递(函数得到参数的副本)和按引用传递(函数得到参数的指针)。在 Python 中,函数得到参数的副本,但是参数始终是引用。因此,如果参数引用的是可变对象,那么对象可能会被修改,但是对象的标识不变。此外,因为函数得到的是参数引用的副本,所以重新绑定对函数外部没有影响。读过《程序设计语言——实践之路(第 3 版)》11(Michael L. Scott 著)之后,尤其是 8.3.1 节“参数模式”,我决定采用共享传参(call by sharing)这个说法。
爱丽丝和白骑士关于那首歌的对话完整版
我喜欢这段对话,但是放在一章的开头太长了。下面是关于白骑士那首歌的完整对话,谈到了曲名和得名的缘由。
“你不开心,”白骑士用一种忧虑的声调说,“让我给你唱一首歌安慰你吧。”
“那首歌很长吗?”爱丽丝问道,因为这一天她已经听过许多诗了。
“是很长,”白骑士说,“不过它非常、非常美。不论谁听到我唱这首歌——或者是听得热泪盈眶,或者是——”
“或者是什么呀?”爱丽丝问道,因为白骑士忽然煞住不言语了。
“或者是没有热泪盈眶,你知道。这首歌的曲名叫作:《黑线鳕的眼睛》。”
“哦,那是一首歌的曲名,是吗?”爱丽丝问道,她试着使自己感到有兴趣。
“不,你不明白,”白骑士说,看来有些心烦的样子,“那是人家这么叫的曲名。
真正的曲名是《老而又老的老头儿》。”
“那么我刚才应该说,‘那首歌是那么被人叫的’?”爱丽丝自己纠正说。
“不,你不应该这么说。这是另一码事!这首歌人家叫作《方法和手段》。不过这只不过是人家这样叫,你知道!”
“嗯,那么,那究竟是什么歌呢?”爱丽丝问道,她这一次完完全全给弄糊涂了。
“我正是准备说的呀,”白骑士说道,“这首歌真正是《坐在大门上》;曲子是我自己发明的。”
——Lewis Carroll
《爱丽丝镜中奇遇记》,第 8 章“这是我自己的发明”
11该书英文版(书名:Programming Language Pragmatics)在 2015 年 12 月已出第 4 版。——编者注
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论