The History of Python

2009年5月1日星期五

元类与扩展类(传说中“杀手的笑话”)

英文原文链接:http://python-history.blogspot.com/2009/04/metaclasses-and-extension-classes-aka.html

原文作者:Guido van Rossum

在Python本来的实现中,类是一类公民对象,可以像处理其它对象一样来操作类。然而创建一个类的过程却固定不变。具体来说,当你定义如下一个类时:


class ClassName(BaseClass, ...):
...method definitions.



类的内容将会运行在它自身的局部字典。类的名字,基类组成的tuple,和这个局部字典将会传递给内部负责生成类对象的类创建函数。类对象生成的动作隐藏在幕后,一般用户对实现细节不需关心。

Don Beaudry是第一个指出如此设计让专业用户失去深层次控制机会的人。具体来说,既然类只是诸多对象的一种,为何不能创建新种类的类来定制其行为呢?他还提出了一个方案,只需对解释器稍加修改就可以利用C代码生成允许新类型的类对象了。关于相关修改的介绍最初出现在1995年,长期被称作“Don Beaudry hook”或者“Don Beaudry hack”,命名不明确是故意开玩笑的结果了。Jim Fulton随后对修改进行了通用性改善,这个修改一直保留在语言中(尽管文档中未说明)直到Python2.2版本为止,那时通过new-style class的引入对元类有了真正的支持(详情见下文)。

Don Beaudry hook的基本想法是,如果在类生成的最后阶段能支持由用户提供的函数,那么专业用户就可以创建定制的类对象。具体来说,如果类名、基类、局部字典可以传递给不同的构建函数,那么该函数在创建类对象时就可以随心所欲,自由发挥了。我唯一的顾虑是不希望由此对类语法做过多更动,毕竟已很稳固。

为了实现目的,这个hook要求用C语言创建一个可被调用(callable)的新类型对象。然后,当这样一个可调用类型的实例在类语句中作为基类时,类创建代码会神秘的调用相应类型对象,而不是标准的类对象。创建类(及其实例)的行为完全依赖于扩展模块提供的可调用对象。

现代Python用户可能会觉得奇怪,感觉有些绕弯。但在当时,类型对象是不可调用的--例如‘int’不是一个内置类型而是一个内置函数,该函数可以返回int对象的实例,而int类型本身即不易存取也难以调用。用户自定义类当然是可以调用的,不过这是因为最初设计类时就将其当做调用指令的特殊情况。Don Beaudry最终说服我接受这个想法,随后又导致了元类和new-style类,并最终导致经典类的消亡。

一开始,只有Don Beaudry自己的Python扩展MESS是唯一利用这一特性的。然而到了1996年,Jim Fulton开发了一个十分流行的第三方模块,称为Extension Classes,也利用了Don Beaudry hook。Extension Classes包在Python2.2引入元类作为对象机制的标准部分后最终失去意义。

在Python1.5中,我去掉了需要写C扩展才能使用Don Beaudry hook的限制。另外为了检查基类类型是否可调用,类生成代码会检查基类中一个名为“__class__”的属性,若存在就调用它。我写了一篇短文介绍这一特性,这是许多Python用户第一次听闻元类的说法。由于这个想法令人头大,很快这篇短文就被冠以昵称“杀手的笑话(The Killer Joke,一个Monty Python里的典故)”。
Don Beaudry hook贡献影响最长的部分应当是类创建函数的API,在Python2.2中新的元类机制中得以保存。如前所述,类创建函数调用时需要三个参数:一个表示类名的string、一个给定基类的tuple(包括基类为空或者只有单个基类情况)和一个内容为方法定义缩进代码块(不仅限于方法,也可以是其它类级别的语句)的字典。类创建函数的返回值是一个类名为名称的变量。

最初,创建类只有简单的内部API。The Don Beaudry hook运用了同样的调用机制,由此成为公开API。这个API重要一点是包含方法定义的块在类创建函数被调用前就执行了。这一点限制了元类的效用,因此元类不能改变方法定义运行时命名空间的初始内容。

Python 3000中对此做了改动,现在一个元类在类体运行时可以提供一个替代映射对象。为支持这一点,明确指定元类的语法也做了变动:在基类中使用关键词参数就是为了这个目的而引入的。

下一篇我会接着写元类如何导致2.2中new-style class的出现(以及在3.0中经典类的消失)。

新闻!有日语翻译版本啦!

英文原文链接:http://python-history.blogspot.com/2009/04/new-now-in-japanese.html

原文作者:Guido van Rossum

现在有一个日语翻译的blog了。耶!还有一个西班牙版本,还有一个法语版本(抱歉,我还不知道具体链接了--知道的请告诉我。)

我对这些语言不了解(或者不熟)因此本文并不代表我“认可”这些翻译,但是我很高兴有这些翻译版本的存在。如果你想开始另外一种语言的翻译,请自便--也请给我一个链接以便我可以更新这个页面。如果你看到翻译里面的不妥之处,请直接和译者联系。(但是,我不希望只是复制我的博文。这样做没有意义,而且一般拷贝热门博文的典型网站都是毫无创新劳动目的只图吸引眼球,以便推销他们的广告。)

大蛇在行动

英文原文链接:http://python-history.blogspot.com/2009/04/and-snake-attacks.html

原文作者:Greg Stein

好,当1995年我第一次接触Python语言的时候,任何把Python和“蛇”联系起来的做法都是禁止的。Python得名于Monty Python,而不是爬行动物。如果有人敢说不,那一定是Knights who say Ni,或者the Rabbit of Caerbannog

再往前,回到1994年,当时我正在LPMUD里面玩的乐不思蜀。那时Web还很罕见,宽带更没听说过。大家在玩这些低带宽的娱乐。

等一下,再往前回一步,到1979年。那时我有了第一台电脑苹果II,Colossal Cave是我最喜欢的游戏之一。之后不久,我学习和玩Zork。我深深迷恋通过计算机和虚拟故事交互并在故事中流连忘返。游戏勾住了我,让我在计算机里面有了另一个生命。(于是,你可以想象时隔四分之一世纪多之后当我遇到Don Woods时是如何的欣喜若狂!)

MUD里面的场景深深打动了我。但是我更想从事建立游戏。我遇到了John Viega,他是一个LPMUD游戏的资深作者、程序员和设计师。当时他在维吉尼亚大学计算机图形实验室工作,参与一个称作Alice的系统。系统设计目的希望降低对计算机知识的要求,需要容易学习以方便用户创见动画人物形象。他们选择了Python语言,这是因为Python的清晰强大易用。

John是Python的极力拥护者,对我说“你一定要学习Python!”“好的,好的。”,我说。这门语言易学易用又功能强大。Python做任何事情都方便。

这是1995年二月的事情了,从那以后我在学习使用Python的路上再没有回头。

当时我根本不知道Python对我的职业和生活有如此关键的作用。感谢Guido,发明了Python。

Python函数编程特性的起源

英文原文链接:http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html


原文作者:Guido van Rossum


不管别人怎么说的想的,我是从来也没想到Python会被函数式编程语言影响如此之深。我对C和Algol 68这类命令式编程语言更为熟悉,而且即使我设定函数为一类公民对象,我也没有将Python看做是函数式编程语言。然而从一开始,就清楚的发现用户希望利用list和函数做更多的事情。


一个对list的常见操作是对其每一个元素调用某个函数并将结果生成一个新的list,例如:



def square(x):
return x*x

vals = [1, 2, 3, 4]
newvals = []
for v in vals:
newvals.append(square(v))

Lisp和Scheme等函数式编程语言会提供内置函数来实现类似操作。因此熟悉这些语言的早期用户会自己动手在Python中实现类似功能的函数,例如:



def map(f, s):
result = []
for x in s:
result.append(f(x))
return result

def square(x):
return x*x

vals = [1, 2, 3, 4]
newvals = map(square,vals)

上述代码的一点小问题是许多人不喜欢应用到列元素的操作被分开定义为一个的单独函数。Lisp等语言允许在进行映射函数调用时直接“当场”定义函数。例如,在Scheme语言中你可以利用lambda在一条语句中创建匿名函数并执行映射操作,像下面这样:



(map (lambda (x) (* x x)) '(1 2 3 4))

虽然Python中函数是一类公民对象,当时并没有任何创建类似匿名函数的机制。


到1993年底,用户已经提出了各种创建匿名函数的思路,和各种list操纵函数如map()、filter()和reduce()。例如,Mark Lutz(《Programming Python》的作者)贴过一段利用exec来产生函数的函数代码:



def genfunc(args, expr):
exec('def f(' + args + '): return ' + expr)
return eval('f')

# Sample usage
vals = [1, 2, 3, 4]
newvals = map(genfunc('x', 'x*x'), vals)

Tim Peters又跟进提出了一个语法上略微简化的方案,允许用户这样写代码:



vals = [1, 2, 3, 4]
newvals = map(func('x: x*x'), vals)


这些都表明了对这一功能确有实际需求。然而当时利用exec手工将包含代码的字符串转换为匿名函数过于“儿戏”。于是1994年1月,map()、filter()和reduce()被添加到标准库。另外,lambda操作符也被语法上直观的引入以创建匿名函数(或称匿名表达式)。例如:



vals = [1, 2, 3, 4]
newvals = map(lambda x:x*x, vals)

这些是早期的重要贡献代码。不幸的是我想不起作者是谁了,而SVN的日志也没有记录。如果是你的话,请留言!


我从来也没有喜欢过“lambda”这个术语,但是又找不到更合适的替代者,于是也只好用了这个。毕竟lambda是当时无名代码贡献者的选择,那时大的改动比现在需要的讨论要少的多,讨论少的影响有好有坏


Lambda最初只用来当做定义匿名函数的语法工具。然而选择lambda这个词带来了许多意外后果。例如熟悉函数式编程语言的用户希望Python中的lambda能和其它语言相似。结果他们发现Python的实现缺少对高级特性的支持。例如一个小问题是lambda表达式无法引用上下文中变量。于是下面代码中map()函数将会出错,提示lambda函数中引用了未定义的变量“a”。



def spam(s):
a = 4
r = map(lambda x: a*x, s)

可以用下面代码来凑活着解决这个问题,不过涉及设置缺省参数和传递隐含参数很不直观:



def spam(s):
a = 4
r = map(lambda x, a=a: a*x, s)

这个问题的“标准”答案是对所有被函数引用的相关上下文中局部变量,内部函数隐含支持对这些变量的引用。这也就是“闭包(closure)”,函数式编程语言中的一个关键特性。然后Python直到2.2版本才引入对闭包的支持(python2.1版本也可以通过“from the future”方式使用)。


有意思的是,map、filter和recude函数,他们是最初引入lambda和其它函数式编程语言特征的源动力,自身却很大程度上被list comprehensions和generator expressions盖过了风头。以至于在Python3.0中reduce函数已经从内置函数列表中移除。(不必担心lambda、map和filer,他们已经被保留了:-)


要提到的是,即使我还是没有把Python当做函数式编程语言的情况下,闭包的引入却成功的用于开发其它高级语言特性。例如new-style class的某些方面,decorator和依赖它的其它现代特性。


最后,即使一部分函数式编程语言特征已引入多年,Python仍然缺乏一些“真正”函数式编程语言的特性。例如Python不支持特定情况的优化(例如尾递归)。总起来说,因为Python的深度动态本质,无法做到Haskell或者ML这类函数式编程语言的编译时优化。这是好事

大改名

英文原文链接:http://python-history.blogspot.com/2009/03/great-or-grand-renaming.html


原文作者:Guido van Rossum

Python初创时期,我总是将其作为一个独立程序来用,偶尔连接一些第三方库进来。因此源代码随意定义全局名称(当然要符合C/连接器要求)例如“object”、“getlistitem”和“INCREF”诸如此类。随着Python流行开来,人们开始要求“嵌入”版本的Python,以便Python自身作为一个库供其它应用程序连接-这和Emacs与Lisp解释器之间混合的方式还不一样。


糟糕的是,嵌入由于命名冲突而复杂化,Python全局名和那些嵌入Python的应用之间会发生名称冲突-特别是“object”很常见。为了解决这个问题而进行了命名约定,约定所有的Python全局名称开头为“Py”或者“_Py”(用于因技术原因不得不作为全局变量的内部名称)或者“PY”(用于宏)。


为了向下兼容(已经有许多第三方扩展模块)和方便核心开发人员转换(在他们的大脑中原来的旧名字已经根深蒂固)分成了两个阶段。第一个阶段连接器看到的是旧名字,而源代码使用的是新名称,用一个包含许多规则的C语言预编译宏来完成转换。第二个阶段连接器看到的是新名字,但是为了继续支持还没移植的原有扩展模块,另外一套宏用于旧名字转换为新名字。在任一阶段,代码都可以在新旧名字混合情况下正常工作。


我研究了一下Subversion日志中名称改变的历史。我发现1995年1月12日的r4583在所有头文件中引入新名称,标志伟大改名(great renaming)第二阶段的开始。然而直到1996年12月C源代码中的改名工作仍在继续进行中。也是大约在这个时候,重命名本身也被重新称呼,checkin comments常称之为“宏大命名(Grand Renaming)”了。在2000年5月,作为Python1.6版本发布的成果之一,向下兼容的宏被移除。check-in comment r15313庆祝了此一事件。


此事归功于Barry Warsaw和Roger Masse,他们做了大量细致的工作,对源文件内容逐个进行重新命名(有些脚本来帮助这一过程)。对大量标准库进行unit test也有助于他们重命名,这是又一个繁琐的工作。


Wikipedia上对Great Renaming有一个早期条目,是指USENET groups的改名。我称Python的改名为Great Renaming时可能正模糊记得这一事情。我还看到有些参考资料指向随后Sphinx的Grand Renaming,这是用于生成Python文档的包。看起来Zope也有一个Grand Renaming,最近Py3k也讨论到了术语PyString到PyBytes的重命名(当然这个和前面那些比要小得多)。


伟大改名或者宏大改名(Great or Grand Renamings)对软件开发社区来说是一个长期的创伤,因为这需要程序员的脑袋接受再教育、重写文档、对改名前提议的补丁集成到到改名后的分支变得困难。(尤其是还存着未改名分支时更是如此。)

2009年4月30日星期四

模块动态载入机制

英文原文链接:http://python-history.blogspot.com/2009/03/dynamically-loaded-modules.html


原文作者:Guido van Rossum


Python的实现架构使得从一开始就容易使用C写扩展模块。然而最初动态连接技术相当不成熟以至于只能在构建时由Python解释器时静态链接扩展。因此C扩展模块需要附带shell脚本用以给Python和它的扩展模块生成Makefile文件。

虽然这个方法对小项目来说可以凑活,Python社区产生新扩展模块的速度之快出乎意料,这样可以单独编译和加载模块就成了迫切需求。很快,有人贡献了平台相关的动态链接API编程接口,允许import语句在磁盘上如同以前处理“.py”文件一样寻找共享库。CVS日志中最早提到动态加载是1992年1月的事情,支持主流操作系统要到1994年底才完成。


动态链接支持被证明是很有用的技术,可惜却带来了维护上的困扰。每个平台有各自独有的API,有的平台还有附加的限制。1995年1月,重构动态链接支持之后,所有相关代码集中在同一个源文件中。然而,这只是集合成一个到处都有条件编译指令(#ifdef)的大文件。1999年12月,在Greg Stein的帮助下,再次重构将每个平台相关的代码保存为专为该平台(或满足同样条件的一系列平台)的设定文件。


虽然Python支持动态加载模块,对许多用户来说,构建这些模块的过程仍然感觉神秘莫测。许多用户,而且数量还在增长开始构建模块--尤其是SWIG为代表的扩展构建工具出现之后。但是,当用户准备发布一个扩展模块时又要面对巨大障碍了,需要处理模块在各种平台、编译器、连接器的各种组合情况。以至于有时用户需要自己动手写Makefile文件和设置编译器连接器参数的配置脚本。还有一种选择就是用户也可以将自己的模块添加到Python的Makefile,然后设定合适选项后执行部分Python重构建。但是这需要用户手头上就有Python源代码。


最终,distuils这一Python扩展构建工具出现了,致力于统一解决模块构建和安装问题。必要的编译器和连接器参数写入一个Python makefile文件作为数据文件,在distutils构建扩展模块时会用到。主要由Greg Ward完成,distutils第一个版本单独发布以支持以前的Python版本。从Python1.6开始,Python把distutils当做标准库模块包含进来。


要提到的是distutils能做的远比从C源代码构建扩展模块要多。它也可以安装纯Python代码的模块和包,创建Windows可执行安装包,以及运行SWIG等第三方工具。哎,distutils由于复杂而被很多人诅咒,也没有得到应有的维护。结果,最近又有第三方替代方案(特别是ez_install,也就是“eggs”)开始流行,可悲的导致了开发社区的分裂,而且也由于同样难以使用在每当不灵的时候就被抱怨。看起来,这问题本质上就是一个难事吧。

2009年4月29日星期三

整数除法问题

英文原文链接:http://python-history.blogspot.com/2009/03/problem-with-integer-division.html


原文作者:Guido van Rossum


Python对整数除法的处理是一个早期犯错导致后患无穷的典型范例。前面已经提到创立Python时我抛弃了ABC中采用的数值处理方法。例如,在ABC中当你进行两个整数相除,结果是一个准确的有理数。而在Python中,整数除法的结果被截断为一个整数。


依据我的经验,ABC中的精确有理数并没有达到设计者的期望。写一个商业应用软件小软件就可以发觉(例如,计算一个人缴税多少),比通常想象的慢太多。调试程序可发现原因在于程序内部在运算时用数千位精度的有理数来表示数值,这些值在最后输出时精度只要求到小数点后两三位就足够了。这个问题可以利用计算起始时附加一个不完全精确的0来解决,但是这个思路对初学者来说并不直观,且难以调试。


所以在设计Python时,我参考了自己已经熟悉的C语言的数值模型。C包括各种长度的整数和浮点数。我就选了C的long类型为Python的整数(integer)(可以保证至少有32位精度),选择C的double表示Python的浮点数(floating point number)。然后我又增加了一个任意精度的整数类型,称为“long。”


我从C语言还借鉴了一条规则:标准的算术运算,包括除法,返回值总是和操作数同一类型,这条在C中实用的规则在Python这种十分高级的语言(very-high-level language)中则铸成大错。更糟的是,我最初还设定了一个禁止混合类型运算的错误规则,当时这么做的目的是为了让各个类型实现之间互不关联。所以,一开始不能计算int和floating相加,即使int和long也不行。Python公开发布之后,Time Peters很快就说服我禁止混合类型运算是个坏点子,于是我引入常用的强制转换规则(coercion rules)来支持混合类型运算。例如int和long的运算会先把int类型的参数转换为long类型,然后返回long类型的运算结果,任何一个参数为float时就把另外一个参数转换为float类型并返回float类型的运算结果。


至此大错已成--整数相除返回整数类型结果。你可能会奇怪“不会是杞人忧天吧?”或者庸人自扰吧?随后为了改变这一设定多次遭受Python社区用户坚韧的反对,他们坚持能明白“整除”的含义对是所有程序员必经的“成人礼”。那么,请让我来解释一下我认为这是一条设计缺陷的原因。


当你写一个实现数值运算的函数(假设,计算月相)你通常希望参数是浮点数类型。然而,由于Python没有类型声明,就无法阻止函数调用者传递整数参数。对于C这类静态类型语言,编译器可以强制转换参数为浮点数,然而这在Python中不存在-算法中各步骤将会一直用整形计算直到遇到和浮点数的混合运行后才会恢复中间计算结果为浮点数类型。

除了除法之外的其它运算,整数和浮点数的行为是一致的。例如1+1等于2,恰如1.0+1.0是2.0,其它运算也是。这就很容易造成一种假象:数值算法的参数是整数还是浮点数无关紧要。然而,当除法参与进来,两个运算数都是整数时,运算结果直接截断,这造成运算中一个潜在质的误差。虽然可以通过在函数入口强制转换所有参数为浮点数来进行保护,这样很不方便,也无助与代码的可读性和可维护性。另外,这样也限制了算法同时支持复数类型参数(当然这种情况很罕见)。


回过来还是要说,问题原因全在于Python不能自动转换参数到预先声明的类型。传递一个无效参数,例如字符串,通常很容易发现,因为很少有运算支持字符串/数值混合类型的运算(除了乘法)。然而传递整数参数时,可能会得到和正确答案相近却可能有相当大误差的结果-很难调试甚至不容易发现问题。(最近我又遇到这样的情况,是在画一个模拟时钟时候--表针的位置由于整除截断而不准确,但是错误却不容易看到,只有每天某些特定时刻才比较明显。)


修正整除错误不是一件简单的事情,因为程序已经依赖整除截断行为了。语言增加了整除运算符(//)来提供原有截断语义。另外通过(“from __future__ import division”)机制可使整数除法使用不再截断的新语义。最后,还可以通过添加命令行参数(-Qxxx)使用新语义或者辅助代码转换到新语义。幸运的是,Python3000中除法的缺省行为是正确的。