The History of Python

The History of Python

2011年7月9日星期六

Karin Dewar, 缩进 和 冒号

Karin Dewar, 缩进 和 冒号

我在另一个博客近期一篇博文中说了一个轶闻,是关于Robert Dewar的妻子如何发明Python缩进的趣事。当时我就提到自己并不十分确认具体细节,现在看来此举颇为明智,因为真相与之颇有偏差。我收到了Lambert Meertens一封详述此事的长电子邮件。我准备在此复述此信,除了部分他要求不要直接引用的部分外基本是信的全部内容了。摘要:Karin Dewar提供了ABC(因为也是Python)中在缩进开始前使用冒号的灵感,而不是缩进本身。下面是Lambert的电子邮件:

    Dewar的贡献不在缩进本身,而是发明了缩进前冒号的使用。

    首先是B中的缩进。在B0(B0,B1,B2, ... 是创建ABC时的一系列设计,B0最早)中就采用了强制缩进来分组代码,并且把分组代码用BEGIN和ENG分隔符包含起来。关于这点可以参考[GM76]的4.1节(布局)。如同打印美观一样,缩进被提议由专门的B编辑器来负责,而用户无法对此修改:用户没有被赋予取消这一设置的选择,以免缩进的强制性被破坏。

    有了强制性缩进后,额外的BEGIN和END分隔符自然就显得多余了,B1时我们就取消了BEGIN,只保留了END IF、END FOR等结束分隔符,在B2时结束分隔符也被取消了,只利用缩进作为分组代码的唯一标识。这一点可参考[ME81]第四章(语句语法)

    ABC中缩进的本意仅是为了使源代码看起来整洁,易于表达语义逻辑,把尚未强制的习俗固化下来,取消额外的BEGIN ... END可能受[PL75]影响,后者提倡仅用缩进来分组代码。occam出现(1983)的要晚,因此缩进这一特性不可能源自occam,同一原因,也不会是借鉴自Miranda(1985)。据我所知,B语言是最早采用缩进作为分组语句特性的且正式发布(并且有实现)的语言

    现在到了 Dewar 的故事,也就是我得知采用冒号的由来,我在此将其记录下来,也算是ABC设计理念的回忆录:


依 Lambert 要求,下面的内容由我组织语句而不是直接引用。

1978年,一次在Jablonna(亚布翁纳)召开的设计会议上,Robert Dewar, Peter King, Jack Schwartz 和 Lambert比较各种为B提议的语法,通过比较采用各种语法实现同样的(代码质量一般的)冒泡排序。他们僵持不下时便去Robert Dewar妻子的房间将她喊来,想听听她的观点,像是现代版的让Paris来从Hera, Athena, 和 Aphrodite之间选美。但是刚刚向她解说了第一个语法形式后,她说道:“你的意思是说,这样一行'FOR i ...',必然会影响接下来的一组语句,而不会仅是影响当前行本身?!”于是,这些科学家们意识到在这样的行结束后附加一个冒号,就能避免误解。

Lambert 还提到如下相关文献:

    [PL75] P. J. Plauger. Signal and noise in programming language. In J. D. White, editor, Proc. ACM Annual Conference 1975, page 216. ACM, 1975.

    [GM76] Leo Geurts and Lambert Meertens. Designing a beginners' programming language. In S.A. Schuman, editor, New Directions in Algorithmic Languages 1975, pages 1–18. IRIA, Rocquencourt, 1976.

    [ME81] Lambert Meertens. Issues in the design of a beginners' programming language. In J.W. de Bakker and J.C. van Vliet, editors, Algorithmic Languages, pages 167–184. North-Holland Publishing Company, Amsterdam, 1981.

############################################################################
小广告:以前留言的朋友,由于博客的缺省设置,可能将部分留言作为广告信息自动屏蔽了,所以我当时没有看到留言,十分抱歉。现在修改了设置,应当能看到了。

2011年3月25日星期五

2011 Language Summit Report

http://blog.python.org/2011/03/2011-language-summit-report.html


2011年语言峰会报告

今年语言峰会于3月10日星期四在亚特兰大召开,在 PyCon 会议前一天。出席人员包括来自 CPythonPyPyJythonIronPythonParrot  等 VM 代表,以及来自 FedoraUbuntuDebian 的系统包维护人员;还有 Twisted 项目开发人员和若干其他人士。

开发 Blog

首先讨论的事项之一正是这个 Blog 本身,由 PSF 通信员 Doug Hellmann 发起。由于 python-dev 邮件列表日常讨论过多,希望借由这个 Blog 来方便用户获取开发资讯。我们计划包含 PEP 、重要决策、新特性与关键 bug 修复,将来还会包括下一步开发计划的非正式通报。

Blog 发文对所有 Python 实现开放。例如,即便 PyPy 当前已有自己的 Blog 且相当活跃,也欢迎他们在这儿发布信息。还有一个相关讨论就是在 python.org 的 download 页面提供对其它 Python 实现的介绍。这些实现的新版本发布时也将会在 python.org 主页作为新闻项目列出。

Compatibility Warnings

在 Python 3.2中我们引入了 ResourceWarning ,用于方便用户定位依赖 CPython reference counting 的代码。 ResourceWarning 不仅有利于用户书写高质量代码,也对书写跨 VM 代码有帮助。为进一步提高跨 VM 兼容性,提出了一个新的警告类型:CompatibilityWarning。

之所以这么做,是源于最近由 PyPy 开发人员提交的一个 CPython bug 。 Issue #11455指出 CPython 允许用户创建一个新类型时,其 __dict__ 可以有“非字符串” key ,而目前至少 PyPy 和 Jython 不支持这样做。现在,用户就可以类似 ResourceWarning 那样抛出 CompatibilityWarning 警告,以便于定位此类问题。

独立的标准库

现在 CPython 源代码已完成从 Subversion 到 Mercurial 的转换过程,再次兴起了将标准库放在单独 repo 的提议。其它实现的开发人员对此十分关注,因为这样将很大程度的简化其开发过程。其它实现现有方法比较繁琐,先对 CPython 的源代码做个快照,然后应用自身实现相关补丁,转换部分 C 扩展为纯 Python 实现,等等。

讨论结果的实施会由接下来的一个相应 PEP 来体现,其中之一是版本号规划问题。既然库不再依附任一具体实现而是独立存在,则应有自己的版本号,以后的测试也应有版本相关考虑。

标准库相关的讨论还涉及纯 Python 代码和对应 C 扩展的问题。PyPy 项目的 Maciej Fijalkowski 指出随着时间的推移,某些模块的 C 与 Python 版本出现细微差异,并建议修订这些模块时采取严谨审慎的策略,以便两种用户(纯 Python 实现的和 C 扩展的)都能平滑升级,这一点也达成了共识。另外,还决定,以纯 Python 实现优先,C 实现只用在能明显增加性能的场合。

测试性能网站

PyPy 的 Speed 中心十分成功的展示了 PyPy 的性能,由此讨论了在 python.org 设立类似网站的想法,初步打算命名为 performance.python.org ,各种 VM 实现均包含进来。不仅进行性能测评,还可包含内存占用、测试通过程度以及语言兼容性等指标。由于当前只包含 PyPy 和 CPython ,为了支持其它 Python 实现还需要进行系统安装设置方面的工作。

Allison Randal 在会上提议了在 Oregon State University 的开源实验室托管若干高性能电脑,恰好可用来作为新的 Speed 中心。Jesse Noller 谈了购置硬件事项--欢迎捐助!

如果您或者您的机构愿意资助 Speed 中心或其它方面的 Python 开发,请联系 Python 软件基金会,这儿是捐赠页面


暂停解除

随着 CPython 3.3 开发的起步,暂停语言变化的限制已经失效。虽然闸门已经打开,语言变化仍然会采取保守态度,我们希望减慢进化的速度,这也有利于其它实现赶上 CPython 。虽然在暂停语言变化期间没有其它实现升级到3.x版本, PyPy 和 IronPython 最近已经实现了兼容2.7版本, IronPython 也开始3.x的规划。

至于 Python 3.3会有哪些变化,首先 PEP 380已被接受,该 PEP 引入了 "yield from " 语法,允许在一个 generator 中 yield 另一个 generator 。除此之外,近期内尚无其它语言变化。

异常属性

接下来简略讨论了异常提供属性以增加易用性的问题,而不局限现在这样迫使用户依赖字符串信息。例如在 ImportError 时,不应通过 parsing 手工分析何处 import 失败,而是提供相应属性。

实现很可能在初始化异常对象时依赖显式关键词( keyword-only )参数,当前已有针对 ImportError 情况的补丁

贡献者协议

与会者还讨论了贡献者协议,并准备若干形式的电子协议。 Google 个人贡献者协议是设计新协议时的参考之一。这个话题的讨论已有很长时间,许多人希望能有电子版本的解决方案。由此,需要调研以保证将来使用电子版本的协议时在美国之外也有效。

Google Summer of Code


Martin von Lowis 简短介绍了今年在 PSF 名下的 Google Summer of Code 。我们鼓励开发者不仅作为导师,还鼓励他们提议项目,便于学生参与--而且提出项目并不意味着一定要作为导师。如果您有兴趣帮助我们,请参阅 PSF 的项目与导师征集通知

Distutils

Tarek Ziade 带来了 Distutils2 ,指出他们正进行的 sprint ,目标在于完成到 Python 3 的移植并为最终合并回 Python 标准库做准备。另外,到时将会采用一个新的名称: packaging 。 packaging 项目组计划还提供单独的 Distutils2 库,以支持 Python 2.4到 Python 3.2。

packaging sprint 是 PyCon sprints 中规模最大的 sprint 之一,结果十分理想。当前代码在 Bitbucket ,只待合并回标准库。

其它VM展望

IronPython提到下一步计划,已包含 3.x 发布。他们在 PyCon 宣布了 2.7.0 ,这也是项目由微软转交为开源社区驱动后第一次新版本发布,然后准备接下来几个月向3.x进发

最近发布了2.5.2,而且计划实现2.6。有些人建议直接跳级到2.7,因为2.6和2.7之间的区别并不大,但是如果跳级,可能接下来新版本的发布会耽搁一段时间。“早发布,多发布”是会上一句格言,他们也有考虑2.6之后直接升级到3.x,然后再考虑2.6到2.7的问题。

开发资助

3.x计划还谈论了如何资助开发,以及资助对加速其它实现赶上3.x版本的重要性。目前有相应资金,需要向 PSF 提交申请后才会进行可行性论证。对此感兴趣,希望得到资金支持者请联系 PSF 。

Python 基准

Jim Fulton 谈到他称作“基准” Python 的概念。在部署 Python 应用时,他发现有的操作系统难以设置 Python 。恰好 Fedora 和 Ubuntu/Debian 的打包专家也在,我们得以进一步考察这个问题。

对 Fedora 而言,基准 Python 安装需要考虑操作系统是利用一张 Live CD 来完成的,因此只能是一个最小安装,和极少的依赖,这种最小化安装简化到仅能使系统运行起来。因此做了目录名称的变动,也没有包含 distutils 等标准库,而且有些库即使包含版本也较陈旧。

当前尚未有一个明晰的解决方案,但相关成员将会进一步研究以期解决该问题。

3.3 特性

会上还讨论了3.3可能引入的特性,包括两个 PEP 。PEP 382 讨论了 Namespace Packages ,会在将来合适时机实施,在合并 distutils 时也有提及。

PEP 393, 定义了一个灵活的字符串表示机制,也引起了讨论,而且有些同学希望将其作为 GSoC 项目。除了功能上的实现之外,还需要考虑性能与内存占用等方面以决定实现能否被接受。

Unladen Swallow

Unladen Swallow 目前处在 “休眠” 状态,当前并不适宜合并到 CPython 3.3。由于缺乏相关领域的专家参与,要取得进展需要数个方面的突破。在讨论时还提到是否资助有利于推动 Unladen Swallow 前进,若有兴趣,致函联系 PSF。

尽管 Unladen Swallow 处在休眠期,且前途未卜,它仍然对 Python 和开源社区很有积极意义。 例如 Unladen Swallow 的性能测评部分就对评测其它实现很有帮助。另外 Unladen Swallow 开发人员对 LLVM 和 Clang 的贡献也对这些项目有帮助。

还有两个性能相关的想法被简单提及,包括 Dave Malcolm 的函数内联提议。 Martin von Lowis 提及了他正在着手的 JIT 扩展模块,虽然 PyPy 开发人员对这种 JIT 的有效性持怀疑态度。

通往异步框架之路

临近结束时讨论了在某种程度上将 Twisted 集成到标准库中来的问题。基本想法是这样一个异步实现有助于把程序迁移到 Twisted 或者其它异步编程框架。

接下来就会有一个 PEP ,和 WSGI 参考类似,但是是针对异步事件循环( asynchronous event loops )的。除PEP 作者外还需要 Twisted 项目和其它相关项目人士须致力于达成共识。

更多信息

更多信息请参考 CPython 开发人员 Nick Coghlan 的笔记要点

2010年8月25日星期三

Why Python's Integer Division Floors

英文原文链接: http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html

原文作者: Guido van Rossum

Why Python's Integer Division Floors
为何Python整除运算采用向下取整的规则

今天(又)有人问我,为什么Python中的整除(integer division)返回值向下取整(floor)而不是像C语言中那样向0取整。

在正整数范围内,两者并无实质差别,例如:

>>> 5//2
2

但是当操作数之一为负时,结果是向下取整的,也就是远离0(接近负无穷方向):


>>> -5//2
-3
>>> 5//-2
-3

或许部分人不太适应,数学上有一个较好的解释为何这样做。整除运算(//)和与之密切相关的取模运算(%)满足如下优美的数学关系式(所有变量均为整数):

a/b = q 余数为 r

b * q + r = a 而且 0 <= r < b (假设a和b都>=0)

如果希望将这一关系扩展到a为负(b仍为正)的情况,有两个选择:一是q向0取整,r取负值,这时约束关系变为 0 <= abs(r) < b,另一种选择是q向下(负无穷方向)取整,约束关系不变,依然是 0 <= r < b。


在数学的数论中,数学家总是倾向于第二种选择(参见如下Wikipedia链接)。在Python语言中我也做了同样选择,因为在某些取模操作应用中a取什么符号并不重要。例如从POSIX时间戳(从1970年初开始的秒数)得到其对应当天的时间。因为一天有24*3600 = 86400秒,这一操作就是简单的t % 86400。但是当表达1970年之前的时间,这时是一个负数,向0取整规则得到的是一个毫无意义的结果!而向下取整规则得到的结果仍然是正确的。

另外一个我能想到的应用是计算机图形学中计算像素的位置。我相信这样的应用还有更多。

顺便说一下,b取负值时,仅需要把符号取反,约束关系变为:

0 >= r > b

那么,现在的问题变成,C为啥不采取(Python)这样的选择呢?可能是设计C时硬件不适合这样做,所谓硬件不适合这样做是说指那些最老式的硬件把负数表示为“符号+大小”而不是像现在的硬件用二进制补码表示(至少对整数是用二进制补码)。我的第一台计算机是一台Control Data大型机,它用1的补码来表示整数和浮点数。60个1的序列表示负0!

Tim Peters对Python的浮点数部分洞若观火,对于我想把这一规则推广到浮点数取模运算有些担心。可能他是对的,因为向负无穷取整的规则有可能导致当x是绝对值特别小的负数时x%1.0会丢失精度。但是这还不足以让我对整数取模,也就是//进行修改。

附言:注意我用了//而不是/,这是一个Python 3 语法,而且在Python 2 中也是有效的,它强调了使用者是要进行整除操作。Python 2 中的 / 有可能产生歧义,因为对两个操作数都是整数时或者一个整数一个浮点数或者两个都是浮点数时,返回的结果类型不同。当然,这是另外的故事,详情参见PEP238

2010年7月6日星期二

From List Comprehensions to Generator Expressions

英文原文链接: http://python-history.blogspot.com/2010/06/from-list-comprehensions-to-generator.html

原文作者: Guido van Rossum

From List Comprehensions to Generator Expressions

从 List Comprehension 到 Generator Expression

List comprehension 在 Python 2.0版本添加进来。这一特性始于Greg Ewing的一套补丁,Skip Montanaro 和 Thomas Wouters参与贡献。(如果我没记错,Tim Peters也很提倡这个想法。)本质上,可以看做众所周知的数学家采用的集合符号的Pythonic化解释。例如,通常认为如下

{x | x > 10}


代表所有满足x > 10的x组成的集合。数学里这种形式隐含了读者可接受的全集(例如根据上下文,可能是所有实数或者所有整数)。在Python中没有全集的概念,在 Python 2.0 时连集合的概念也没有。(Sets是一个有趣的故事,我将来会在另一篇博文讨论。)

基于此以及其它方面考虑,Python中采用如下语法形式来表示:

[f(x) for x in S if P(x)]


这条语句产生一个list(列表),包含的值来自 sequence (序列) S,满足 predicate (判定) P,且被function(函数) f map (映射)。if-从句是可选项,且可以存在多个for-从句(每个for-从句可以有自己可选的if-从句)来表示嵌套循环(多for-从句会把多维元素映射到一维列表中,这一需求比较少见,因此实用中不常用)。

List comprehension 提供了内置函数map() 和 filter() 的替代。 map(f, S) 等价于 [f(x) for x in S],filter(P, S) 等价于 [x for x in S if P(x)]。或许有人认为 map() 和 filter() 的语法形式更紧凑,所以 list comprehension 没有多少值得推荐的。然而,如果考察一个更加实际的例子,观点或许就会改变了。假设我们想对一个list中的每个元素增加1,生产一个新的list。list comprehension 的写法是 [x+1 for x in S] 。map() 的写法是 map(lambda x: x+1, S)。这儿的"lambda x: x+1" 部分是Python语法中用于内嵌的匿名函数。

两种形式(list comprehension和map()/reduce())孰优孰劣引起了争论,有人认为争论的关键在于 Python 的 lambda 语法过于繁琐,如果匿名函数能有更简洁的表示形式,那么map()就更有吸引力了。我不同意,我发觉 list comprehension 形式比函数式语法更易读,尤其是当映射函数变得复杂时。另外 list comprehension 比 map和lambda 执行速度更快。这是因为调用一个 lambda 函数就创建了一个新的堆栈结构(stack frame),而 list comprehension 中的表达式无需创建新的堆栈结构。

在list comprehension获得成功,在发明generator(关于generator,将来会在另外一篇展开)之后,Python 2.4 增加了一种近似的语法用以表示结果构成的序列(sequence),但是并不将它具体化(concrete)为一个实际的list。新特征称作 "generator expression"。例如:


sum(x**2 for x in range(1, 11))


这条语句调用内置函数 sum(),参数为一个generator expression, 它 yield 从1到10(包括10)的平方。 sum() 函数把参数中的值加和起来,得到答案385。该语句相对于 sum([x**2 for x in range(1, 11)]) 的优势应当是明显的。后者生成了一个包含所有平方数的list,然后再遍历一次,最后(得到结果后)丢弃该list。对于数量较大的数据,前者在内存方面的节省是一个重要考虑因素。

我还应该提到 list comprehension 和 generator expression 微妙的区别。例如,在Python 2,如下是一个有效的 list comprehension:


[x**2 for x in 1, 2, 3]


然而,这是一个无效的 generator expression:


(x**2 for x in 1, 2, 3)


我们可以通过给"1, 2, 3"部分添加括号来修复它:


(x**2 for x in (1, 2, 3))


在Python 3,你甚至对list comprehension也必须使用括号了:


[x**2 for x in (1, 2, 3)]


然而,对于"常规的"或者"显式的"for-循环,你仍然可以省略括号:


for x in 1, 2, 3: print(x**2)


为何有这种区别,而且为何在Python 3 对 list comprehension 变得更严格了?影响设计包括反向兼容,避免歧义,注重等效,和语言的进化等因素。最初,Python(还没有版本号的时候:-)只有明确的for-循环形式。在'in'之后的部分不会带来歧义,因为它总是最后伴随着一个冒号。我清楚你要做的是对一些已知数值进行循环,因此,你不需要因为必须增加括号而烦恼。写到这里,又让我想起来在Algol-60,你可以这样写:


for i := 1, 2, 3 do Statement


Algol-60 还额外支持利用step-until从句决定表达式的步长,如下:


for i := 1 step 1 until 10, 12 step 2 until 50, 55 step 5 until 100 do Statement


(追忆往事,如果当初Python的foo-循环也能这样支持对多个序列的遍历,也挺酷的,哎。。。)

当我们在Python 2.0 中增加 list comprehension 时,原来的规则依然有效:序列表达式只可能被伴随的右中括号 ']' 或者 'for' 关键词或者 'if' 关键词结束。而且这是好事。

但是,到了 Python 2.4 增加 generator expression 时,我们遇到了歧义性方面的问题: 语法上看一个 generator expression 的括号部分并不是它语法上必须的部分。例如下面例子:


sum(x**2 for x in range(10))


外括号是属于被调用的函数sum()的一部分,里面的 "裸" generator expression 作为第一个参数。因此理论上,如下语句可以有两种解释:


sum(x**2 for x in a, b)


可以有意解释为这样:


sum(x**2 for x in (a, b))


也可以解释为:


sum((x**2 for x in a), b)


(如果我没记错)犹豫了一阵子之后,我决定这种情况不应该猜测,而是 generator comprehension 的 'in' 关键词之后必须是单个表达式(当然,它是 iterable 的)。但是当时我们也不想破坏已存在于 list comprehension 中的代码,因为它已经广为流行了。

设计 Python 3 时,我们决定 list comprehension:


[f(x) for x in S if P(x)]


完全等价于如下利用内置函数 list() 展开的 generator expression:


list(f(x) for x in S if P(x))


于是我们将稍微更严格的 generator expression 语法也同样适用于 list comprehension。

在 Python 3 我们还做了另外的变动,以增加 list comprehension 和 generator expression 的等效程度。Python 2时,list comprehension 会"泄露" 循环控制变量到外边:


x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'


这是最初的 list comprehension 实现造成现象;也是数年间 Python 的"肮脏的小秘密"之一。开始由于这样可以使得 list comprehension 性能"越快越好"而作为折衷保留了,虽然偶然会刺痛人毕竟对新手来说算不上常见缺陷。然而对于 generator expression 我们不会再这样做了。 Generator expression 利用 generator 实现,执行 generator 时需要一个隔离的执行帧(separate execution frame)。由此还使得 generator expression (特别是在遍历比较短的序列时) 比 list comprehension 效率略低。

然而到了 Python 3,我们决心利用和 generator expression 的同样实现策略来修缮这个 list comprehension 的 "肮脏的小秘密"。 于是在 Python 3,上例(当然,最后一句修改为 print(x) :-) 将会打印 'before', 这证明 list comprehension 中的'x'只是暂时遮蔽而不是直接使用 list comprehension 之外的'x'。

在你开始担心 list comprehension 在 Python 3 中变慢之前要说的是:感谢大量的 Python 3 实现方面的努力,list comprehension 和 generator expressions 都比 Python 2 中更快了!(而且两者也不再有速度上的差别。)

更新: 当然,我忘了说 Python 3 还支持 set comprehension 和 dictionary comprehension。这是 list comprehension 思路的自然推广。

Method Resolution Order

Method Resolution Order

英文原文链接: http://python-history.blogspot.com/2010/06/method-resolution-order.html
原文作者: Guido van Rossum

方法解析顺序

在支持多重继承的编程语言中,查找方法具体来自那个类时的基类搜索顺序通常被称为方法解析顺序(Method Resolution Order),简称MRO。(Python中查找其它属性也遵循同一规则。)对于只支持单重继承的语言,MRO十分简单;但是当考虑多重继承的情况时,MRO算法的选择非常微妙。Python先后出现三种不同的MRO:经典方式、Python2.2 新式算法、Python2.3 新式算法(也称作C3)。Python 3中只保留了最后一种,即C3算法。

经典类采用了一种简单MRO机制:查找一个方法时,搜索基类简单的深度优先从左到右。搜索过程中第一个匹配的对象作为返回结果。例如,考虑如下类:


class A:
def save(self): pass

class B(A): pass

class C:
def save(self): pass

class D(B, C): pass


对于类D的实例x,它的经典MRO结果是类D,B,A,C顺序。因此查找方法x.save()将会得到A.save()(而不是C.save())。这个机制对于简单情况工作良好,但是对于更复杂的多重继承关系,暴露出的问题就比较明显了。其中问题之一是关于"菱形继承"时的方法查找顺序。例如:


class A:
def save(self): pass

class B(A): pass

class C(A):
def save(self): pass

class D(B, C): pass


此处类D继承自类B和类C,类B和类C均继承自类A。应用传统的MRO,查找方法时在类中的搜索顺序是D, B, A, C, A。因此,语句x.save()将会如前一样调用A.save()。然而,这可能非你所需!既然B和C都从A继承,别人可以争辩说重新定义的方法C.save()可以被看做是比类A中的方法"更具体"(实际上,有可能C.save()会调用A.save()),所以C.save()才是你应该调用的。如果save方法是用于保持对象的状态,不调用C.save()将使C的状态被丢弃,进而程序出错。

虽然当时的Python代码很少存在这种多重继承代码,"新类"(new-style class)的出现则使之成为常见现象。这是由于所有的新类都继承自object这一基类。因此涉及到新类的多重继承总是会产生前面所述的菱形关系。例如:

class B(object): pass

class C(object):
def __setattr__(self, name, value): pass

class D(B, C): pass


而且,object定义了一些方法(例如__setattr__())可以由子类扩展,这时解析顺序更是至关重要。上面代码所属例子中,方法C.__setattr__应当被应用到类D的实例。

为了解决在Python2.2引入的新类所带来的方法解析顺序问题,我采取的方案是在类定义时就计算出它的MRO,并存储为该类对象的一个属性。官方文档中MRO的计算方法为:深度优先,从左到右遍历基类,这个与经典MRO一致,但是如果任何类在搜索中是重复的,只有最后一个出现的位置被保留,其余会从MRO list中删除。因此我们前面这个例子中,搜索顺序将会是D, B, C, A(经典类采用经典MRO,则会是D, B, A, C, A)。

实际上MRO的计算比文档所说的要更复杂。我发现某些情况下新的MRO算法结果不理想。因此还存在一个特例,用于处理两个基类在两个不同的派生类中顺序不同,而这两个派生类又被另外一个类继承的情况。如下代码所示:


class A(object): pass
class B(object): pass
class X(A, B): pass
class Y(B, A): pass
class Z(X, Y): pass


利用文档中描述的新MRO算法,Z关于这些类的MRO为Z, X, Y, B, A, object。(这儿的object是通用基类。)。然而,我不希望结果中B出现在A之前。因此实际的MRO会交换其顺序,产生Z, X, Y, A, B, object。直观上说,算法尝试保持基类在搜索时过程中首次出现的顺寻。例如,对于类Z,他们基类X应当被首先搜索到,因为在继承的list中排序最靠前。既然X继承自A和B,MRO算法会尝试保持其顺寻。这是在我在Python2.2中实际实现的算法,但是文档只提到了前面的不包括特例处理的算法(我幼稚的认为这点小差别不需明言。)。

然而,就在Python 2.2 引入新类不久,Samuele Pedroni就发现了文档中MRO算法与实际代码中观察到结果不一致的现象。而且,在上述特例之外也可能发生不一致情况。详细讨论的结果认为Python2.2采用的MRO是坏的,Python应当采用C3线性化算法,该算法详情见论文"A Monotonic Superclass Linearization for Dylan"(K. Barrett, et al, presented at OOPSLA'96)。

本质上,Python 2.2 MRO的主要问题在于不能保持单调性(monotonicity)。在一个复杂的多层次继承情况,每个继承关系都决定了一个直接的查找顺序,如果类A继承类B,则MRO明显应当先查找A后查找B。类似,如果类B多重继承类C和类D,则搜索顺序中类B应该在类C之前,且类C应该在类D之前。

在复杂的多层次继承情况,始终能满足这一规则就称为保持了单调性。也就是说,当你决定类A会在类B之前查找到,应当再也不会遇到类B需要在类A之前查找的情况(否则,结果是未定义的,应该拒绝这种情况下的多层次继承)。以前的MRO算法未能做到这一点,新的C3算法则在保证单调性方面发挥了效用。基本上,C3的目的在于让你依据复杂多层次继承中所有的继承关系进行排序,如果所有顺序关系都能满足,则排序结果就满足单调性。否则,无法得到确定的顺序,算法会报错,拒绝运行。

于是,在Python 2.3,摈弃了我手工作坊的 2.2 MRO算法,改为采用经过学术审核检验的C3算法。带来的结果之一就是当多层次继承中存在基类顺序不一致情况时,Python将会拒绝这种类继承。还是参加前面例子,对于类X和类Y就存在顺序上不一致,对于类X,规则判定类A应该在类B之前被检查。然而对于类Y,规则认为类B应该在类A之前被检查。单独情况下,这种不一致是可以接受的,但是如果X和Y共同作为另外一个类(例中定义的类Z)的基类,C3算法就会拒绝这种继承关系。这个也算是对应了Zen of Python的"errors should never pass silently"规则。

2010年7月5日星期一

import antigravity

英文原文链接: http://python-history.blogspot.com/2010/06/import-antigravity.html
原文作者: Guido van Rossum

反重力(antigravity)模块源自一幅XKCD漫画,由Skip Montanaro 添加至Python 3中。进一步的详情可以参考如下链接,这是我所知道最早提及此事的出处: http://sciyoshi.com/blog/2008/dec/30/import-antigravity/


但是反重力(antigravity)模块实际起源于更早时候的Google App Engine!App Engine于2008年4月7号发布,反重力(antigravity)模块在临近发布前才添加进来。在距离发布前几周时间,App Engine大多数代码已经冻结,Google的App Engine项目组认为我们应对添加一个复活节彩蛋,当时征集到许多提案,有的过于复杂,有的难于理解,还有些则存在危险性,最后我们选择了“反重力(antigravity)”模块。App Engine的反重力(antigravity)模块比Python3中的对应实现稍微多了一点变化。它定义了一个fly函数可以随机的做如下两件事情之一:有10%的可能重定向到XKCD的反重力(antigravity)漫画;另外90%的可能则是简单的把漫画中的文字显示在HTML页面中(最后一行有漫画链接地址)。要在App Engine的应用中调用反重力(antigravity)模块,需要如下简单代码:


import antigravity

def main():
antigravity.fly()

if __name__ == '__main__':
main()



更新: Python 3 标准库中的反重力模块还有一个彩蛋中的彩蛋,如果你查看源码会发现它定义了一个实现XKCD 中 GEO 哈希算法(geohashing)的函数.

import this and The Zen of Python

英文原文链接: http://python-history.blogspot.com/2010/06/import-this-and-zen-of-python.html

原文作者: Guido van Rossum

Barry Warsaw 撰写了一篇关于import this和the Zen of Python的有趣博文,我希望更多人关注Python history上由于年代久远而变得晦暗的这一段: http://www.wefearchange.org/2010/06/import-this-and-zen-of-python.html