本章介绍了数据模型的几个特殊方法,因此主要参考资料与第 1 章一样,阅读那些资料能对这个话题有个整体了解。方便起见,我再次给出之前推荐的四个资料,同时再多加几个。
Python 语言参考手册中的“Data Model”一章(https://docs.python.org/3/reference/datamodel.html)
本章用到的方法大部分见于“3.3.1. Basic customization”(https://docs.python.org/3/reference/datamodel.html#basic-customization)。
《Python 技术手册(第 2 版)》,Alex Martelli 著
虽然这本书只涵盖 Python 2.5(第 2 版),但是对数据模型做了深入说明。基本的概念都是一样的,而且自 Python 2.2 起(这一版的内置类型和用户定义的类兼容性变得更好),数据模型的大多数 API 完全没变。
《Python Cookbook(第 3 版)中文版》,David Beazley 和 Brian K. Jones 著
通过诀窍来演示现代化的编程实践。尤其是第 8 章“类与对象”,其中有好几个方案与本章讨论的话题有关。
《Python 参考手册(第 4 版)》,David Beazley 著
详细说明了 Python 2.6 和 Python 3 的数据模型。
本章涵盖了与对象表示形式有关的全部特殊方法,唯有 __index__ 除外。这个方法的作用是强制把对象转换成整数索引,在特定的序列切片场景中使用,以及满足 NumPy 的一个需求。在实际编程中,你我都不用实现 __index__ 方法,除非决定新建一种数值类型,并想把它作为参数传给 __getitem__ 方法。如果好奇的话,可以阅读 A.M.Kuchling 写的“What's New in Python 2.5”(https://docs.python.org/2.5/whatsnew/pep-357.html),这篇文章做了简要说明;此外,还可以阅读“PEP 357—Allowing Any Object to be Used for Slicing”(https://www.python.org/dev/peps/pep-0357/),这份 PEP 从 C 语言扩展的实现者和 NumPy 的作者 Travis Oliphant 的角度详述了对 __index__ 方法的需求。
意识到应该区分字符串表示形式的早期语言是 Smalltalk。1996 年,Bobby Woolf 写了一篇题为“How to Display an Object as a String: printString and displayString”的文章(http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf),他在这篇文章中讨论了 Smalltalk 对 printString 和 displayString 方法的实现。在 9.1 节说明 repr() 和 str() 的作用时,我从这篇文章中借用了言简意赅的表述,即“便于开发者理解的方式”和“便于用户理解的方式”。
杂谈
特性有助于减少前期投入
在
Vector2d类的第一版中,x和y属性是公开的;默认情况下,Python 的所有实例属性和类属性都是公开的。这对向量来说是合理的,因为我们要能访问分量。虽然这些向量是可迭代的对象,而且可以拆包成一对变量,但是还要能够通过my_vector.x和my_vector.y获取各个分量。如果觉得应该避免意外更新
x和y属性,可以实现特性,但是代码的其他部分没有变化,Vector2d的公开接口也不受影响,这一点从 doctest 中可以得知。我们依然能够访问my_vector.x和my_vector.y。这表明我们可以先以最简单的方式定义类,也就是使用公开属性,因为如果以后需要对读值方法和设值方法增加控制,那就可以实现特性,这样做对一开始通过公开属性的名称(如
x和y)与对象交互的代码没有影响。Java 语言采用的方式则截然相反:Java 程序员不能先定义简单的公开属性,然后在需要时再实现特性,因为 Java 语言没有特性。因此,在 Java 中编写读值方法和设值方法是常态,就算这些方法没做什么有用的事情也得这么做,因为 API 不能从简单的公开属性变成读值方法和设值方法,同时又不影响使用那些属性的代码。
此外,本书的技术审校 Alex Martelli 指出,到处都使用读值方法和设值方法是愚蠢的行为。如果想编写下面的代码:
--- >>> my_object.set_foo(my_object.get_foo() + 1) ---这样做就行了:
--- >>> my_object.foo += 1 ---维基的发明人和极限编程先驱 Ward Cunningham 建议问这个问题:“做这件事最简单的方法是什么?”意即,我们应该把焦点放在目标上。11 提前实现设值方法和读值方法偏离了目标。在 Python 中,我们可以先使用公开属性,然后等需要时再变成特性。
私有属性的安全性和保障性
Perl 不会强制你保护隐私。你应该待在客厅外,因为你没收到邀请,而不是因为里面有把枪。
——Larry Wall
Perl 之父Python 和 Perl 在很多方面的做法是截然相反的,但是 Larry 和 Guido 似乎都同意要保护对象的隐私。
这些年我教过许多 Java 程序员学习 Python,我发现很多人都对 Java 提供的隐私保障推崇备至。可事实是,Java 的
private和protected修饰符往往只是为了防止意外(即一种安全措施)。只有使用安全管理器部署应用时才能保障绝对安全,防止恶意访问;但是,实际上很少有人这么做,即便在企业中也少见。下面通过一个 Java 类证明这一点(见示例 9-15)。
示例 9-15 Confidential.java:一个 Java 类,定义了一个私有字段,名为
secretpublic class Confidential { private String secret = ""; public Confidential(String text) { secret = text.toUpperCase(); } }在示例 9-15 中,我把
text转换成大写后存入secret字段。转换成大写是为了表明secret字段中的值全部是大写的。我们要使用 Jython 运行 expose.py 脚本才能真正说明问题。那个脚本使用内省(Java 称之为“反射”)获取私有字段的值。expose.py 脚本的代码在示例 9-16 中。
示例 9-16 expose.py:一段 Jython 代码,从另一个类中读取一个私有字段
import Confidential message = Confidential('top secret text') secret_field = Confidential.getDeclaredField('secret') secret_field.setAccessible(True) # 攻破防线 print 'message.secret =', secret_field.get(message)运行示例 9-16 得到的结果如下:
$ jython expose.py message.secret = TOP SECRET TEXT字符串
'TOP SECRET TEXT'从Confidential类的私有字段secret中读取。这里没有什么黑魔法:expose.py 脚本使用 Java 反射 API 获取私有字段
'secret'的引用,然后调用'secret_field.setAccessible(True)'把它设为可读的。显然,使用 Java 代码也能做到这一点(不过所需的代码行数是这里的三倍多,参见本书代码仓库里的 Expose.java 文件,https://github.com/fluentpython/example-code)。如果这个 Jython 脚本或 Java 主程序( 如
Expose.class) 在 SecurityManager(http://docs.oracle.com/javase/tutorial/essential/environment/security.html)的监管下运行,.setAccessible(True)这个关键的调用就会失败。但是现实中,很少有人部署 Java 应用时会使用 SecurityManager,Java applet 除外(还记得这个吗?)。我的观点是,Java 中的访问控制修饰符基本上也是安全措施,不能保证万无一失——至少实践中是如此。因此,安心享用 Python 提供的强大功能吧,放心去用吧!
11参见“Simplest Thing that Could Possibly Work: A Conversation with Ward Cunningham, Part V”(http://www.artima.com/intv/simplest3.html)。