The History of Python

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中除法的缺省行为是正确的。

用类来表示异常

英文原文链接:http://python-history.blogspot.com/2009/03/how-exceptions-came-to-be-classes.html

原文作者:Guido van Rossum

在初期我就希望Python用异常来处理出错。然而让异常有效工作的关键之一便是设定识别不同种类异常的方案。现代语言(包括现代Python:-),异常是通过用户自定义类来表示的。然而在Python的早期版本,我是用字符串来表示异常的。这很糟糕,但是我有两个借口来解释为何这样做。首先我是从Modula-3中学习异常的,那里就是用特定标识符来表示异常。其次,我引入异常时,还没有引入用户自定义类。

理论上我是可以建立一个新的内置对象类型来用于表示异常,然而实现内置类型需要相当的C语言编程工作,我决定还是重用已有的内置类型。而且,既然异常时总有伴随的出错信息,看起来用字符串来表示异常也很自然。

不幸的是,我选择的语义是不同字符串对象表示不同异常,即使两者有相同的值(即由同样的字符序列构成)。我之所以选择这样的语义是因为我希望定义在不同模块的异常即使值相同仍然互相独立。意思是说,异常是通过对其名字的引用(异常对象)来判定,而不是通过它的值(字符串值)。

这个方法受到了Modula-3异常机制的影响,在Modula-3中每个异常声明都建立一个唯一的不会和其它异常混淆的“异常标识符”。我想我也希望能优化异常处理,就是通过指针比较而不是串比较,这样做属于过早优化运行时间的一个错误尝试(罕见-我通常是优化自己写代码的时间!)。当然主要的原因还是我担心不同模块的无关异常会发生同名冲突。我设定的应用模式是在某一模块中定义异常,然后在所有代码中将其作为全局常量来抛出或捕获。(这也比字串值会自动“interned”早很多。)

哎,真是好事多磨。早期的Python用户发现在同一个模块内,字节码编译器会将值相等的字符串统一表示(也就是说创建单一的对象来表示所有值相同的字符串)。因此,用户偶尔会发现异常可被指定异常名捕获,也可以被同值的字符串捕获。好吧,至少看起来大多数情况下是工作的。但是实际上只是在同模块情况下才存在这一情况--如果要捕获其它模块定义的异常错误信息,就会发生神奇的实效。不必说,这引起了普遍的困扰。

1997年在Python1.5版本我引入类异常到语言中。虽然类异常从此就是推荐方法,字符串异常也为了继续支持一些已有应用代码而被支持,直到Python2.5。最终在Python2.6才移除了字符串异常。

2009年4月28日星期二

为何实现一切皆成为可执行语句

英文原文链接:http://python-history.blogspot.com/2009/03/how-everything-became-executable.html


原文作者:Guido van Rossum


Python新用户有时对语言的任一部分,包括函数和类定义,均能作为可执行语句产生惊奇。这意味着任何语句可以出现在程序的任意地方。例如,如果需要,函数定义可以出现在“if”语句中。


Python语法的初期版本,情况并不是这样的:具有“申明特征”的语法元素,例如import语句和函数定义,只允许出现在模块和脚本(被执行的那个程序)的顶级。然而,当我增加类的时候,我觉得这样过于限制。

我的推理过程如下。与其定义类为纯粹的一系列函数定义语句,看起来允许赋值常规变量也有意义。然而,既然允许这样,我何不更进一步,允许任意的可执行代码呢?或者,更彻底一些呢,例如允许在“if”语句中允许函数定义?很快就发现这样可用于简化语法,对它们可以采用相同的字节码生成函数。


虽然这个原因使我简化了语法,也允许用户在任意位置放置Python语句,这个特性并不意味着一定要支持某个特定编程风格。例如,Python语法在技术上允许定义嵌套函数,然而Python并不支持嵌套命名空间的潜在语义。因此,类似的代码常会运行怪异或者“坏掉”,毕竟不能和那些专门设计出来的语言特性相比。随着时间的过去,许多“坏掉”的特性被修正。例如嵌套函数定义在Python2.1之后才开始工作的正常起来。

一切都是一类公民

英文原文链接:http://python-history.blogspot.com/2009/02/first-class-everything.html

原文作者:Guido van Rossum

[朋友们,请不要在这个Blog的评论部分提问题。如果你想建议这个Blog将来写些什么,给我发email(用Google可以搜到我的主页,那儿有我的email地址。)如果你想提出Python的改进建议或者讨论某种替代设计的价值,用python-ideas邮件列表在python.org。]

我的Python设计目标之一就是所有的Python对象都是“一类公民”。也就是说我希望所有可以在语言中命名的对象(例如整数、字符串、函数、类、模块、方法等)都有同样的地位。这样他们都可以有赋值给变量、放入list、存储在dictionary、传递给参数等操作。

Python的内部实现机制使得这个目标变得简单。所有的Python对象都基于一个共同的C语言数据结构,可在解释器中任意地方调用。变量,list、函数以及其它任一对象都是这个数据结构的变种--这个结构是表示一个如整数这样简单的对象还是像类这样复杂的对象并不重要。

虽然拥有“一切都是一类公民”想法在概念上很简单,仍然有一点和类相关的细微之处值得我再谈谈--也就是让方法成为“一类公民”对象的问题。

考虑如下简单的代码(从上周的博文拷贝而来):


class A:
def __init__(self,x):
self.x = x
def spam(self,y):
print self.x, y


如果方法是“一类公民”对象,那么在Python中它就应该可以像其它对象那样赋给其它变量。例如,一个人可以写“s = A.spam”的代码。这时变量“s”代表了类的一个方法,实际上是一个函数。然而方法和一个普通函数不尽相同。具体来说,方法的第一个参数应当是方法被定义时所在类的实例。

为了解决这个问题,我创建了一个新的类型,它是可调用对象,称为“未绑定方法(unbound method)”。一个未绑定方法实际上是实现方法的函数对象的简单封装,强制第一个参数必须是方法所在类的实例。因此,需要像调用函数那样调用未绑定方法“s”时,就必须传递类A的一个实例作为第一个参数。例如"a = A(); s(a)"[译者注:根据上下文,为能运行,需补全参数,如“a = A(1); s(a, 2)”]。 (*)

一个相关联问题在Python语句是某对象特定实例的方法时会遇到。例如,可以创建一个实例“a=A()”然后再写一句“s = a.spam”。此处变量“s”还是指向类的spam方法,然而这次“s”是通过类的实例“a”得到方法引用的。为了处理这种情况,创建了另一种被称为“绑定方法(bound method)”的可调用对象。这个对象也是表示方法的函数对象的简单封装。但是这次封装隐含的将得到方法时用到的实例存储起来。因此,对随后的“s()”语句,将会在调用方法时把实例“a”作为隐含的第一个参数。

实际上,表示绑定和未绑定方法是同一个内部对象类型。该对象一个某个属性指向对实例的引用。设置为None时,方法是未绑定的。否则就是绑定的。

虽然绑定与否可能看起来没什么重要的,确是类内部工作的一个关键部分。当程序中出现“a.spam()”时,这条语句的执行实际上分为两步。首先,查找“a.spam”发生之处。返回一个绑定方法--这是一个可调用对象。然后,在附加用户提供的参数之后,对这个对象进行函数调用“()”
__________(*)
在Python 3000中,未绑定方法的概念被去除,表达式“A.spam”返回一个简单的函数对象。这是以前的应用经历证明的:由于限制第一个参数必须是A的一个实例对于诊断问题帮助甚微,反而经常成为高级应用的障碍--这种高级应用有人称作“self的鸭子类型(duck type self)”,看起来是个不错的名字。

增加对用户自定义类的支持

英文原文链接:http://python-history.blogspot.com/2009/02/adding-support-for-user-defined-classes.html

原文作者:Guido van Rossum

信不信由你,Python在CWI开发了一年之后才增加了类(class),当然这仍在首次公开发布Python之前了。要知道如何添加类,有必要先知道一些Python的实现细节。

Python是用C语言实现的,属于一个典型的基于堆栈的字节码解释器也可以称作“虚拟机”外加一些也是用C实现的基本类型。虽然Python中到处都是对象(“object”),然而C语言中并不直接支持对象而是通过结构(structure)和函数指针(function pointer)来实现。Python虚拟机对每个对象类型定义了几十个必须或者可选支持的操作(例如,“get attribute”、“add”和“call”)。于是一个对象类型就表示为一个含有一系列函数指针的静态分配结构,每个函数指针对应一个标准操作。这些函数指针通常初始化为静态函数。但是对可选操作,类型对象选择不做对应实现时可以将其函数指针指向空地址。调用该操作时虚拟机或者产生一个运行时错误,或者在某些情况下提供一个该操作的缺省实现。一个类型的结构还包含各种数据域,其中之一是对该类型特有方法list的引用,表示为一个结构数组,该数组包含string(作为函数名)和函数指针(对应函数实现)。Python特有的自省就源于一个对象可以在运行时如同访问其它对象一样访问自身类型的结构。

Python实现的重要一个特点就是完全以C为中心。实际上所有的标准操作和方法(method)都是用C语言的函数现的。最初字节码解释器只能支持纯Python的函数或者C实现的函数和方法。我记得是同事Siebren van der Zee第一个建议Python应当允许和C++类似的类定义以便在Python代码中定义对象。

在实现用户定义对象时我遵守了设计尽量简单原则;方案是使用一种新的内置(built-in)对象来表示该对象,它存储类引用作为指向为该类所有实例共享“类对象”的指针,配合一个“实例字典”来保存实例的变量。

这个实现方案中,实例字典包括每个单独对象的实例变量而类对象则包括该类所有实例的共享部分--特别是方法。在实现类对象时我再次运用设计尽量简单原则;一个类拥有的方法存储在一个字典中,以函数名为key。于是我配套了一个类字典。为了支持继承,类对象还需要另外保存表示基类对象的引用。当时我对类所知甚少,却也知道C++刚刚支持的多重继承。我想既然要支持继承,最好也支持一个简单版本的多重继承。于是每个类对象可以有一个或多个基类。

在这个实现中对象操作的机理十分简单。对实例和类变量的操作只是简单的对应到其字典对象上。例如,给实例变量赋值就是更新它的局部实例字典。同样的,在实例对象中查找变量的值只是简单检查它的实例字典中对应变量。如果没有找到该变量,事情会变得复杂一点。这时会在类字典中进行查找,然后遍历类的基类类字典进行查找。

查找类对象及基类属性(attribute)的过程通常都是和定位方法一致的。如前所述,方法存储在类对象字典里为该类所有实例共享。因此,调用一个方法时,通常在具体实例对象的实例字典里面是找不到对应入口。相反,你需要先在类字典里面查找,然后顺序在其基类的类字典里查找,一旦找到就立即停止查找过程。对每个基类来说也递归的实现同样的查找策略。这个策略通常称为深度优先,从左至右,也是Python原来大多数版本采用的缺省方法解析顺序(MRO:method resolution order)。新近的Python版本采用了一个更复杂的MRO,后面的博文会讨论到。

实现类时尽量简单是我的目标之一。因此Python定位方法时不执行复杂的错误检查或者一致性检查。例如,如果某个类重载了基类的某个方法,并不进行检查以确保重新定义的方法和原基类实现有相同的参数个数或者一致的调用方式。前述的方法解析算法只是简单的返回第一个找到的结果,然后不论有什么参数,都在调用该结果时提供给它。

其它一些特性也在设计中引入。例如,虽然类字典最初设计时是为了存储方法的,也没有理由说一定不能存储其他类型的对象。因此,如果某个integer或者string对象存储在类字典中,它就成为一个类变量--它不存储在每个实例变量中,而是存在类中为所有该类实例共享。

类的实现不仅简单,还提供了令人惊奇的灵活性。例如,具体实现不仅使得类成为“一类公民”从而在运行时很容易进行自省,还使得动态进行类的修改成为可能。例如类对象已经生成之后,通过简单的增加或者修改类字典就可以修改类的方法!(*) Python的动态本质使得修改立刻对所有该类及其子类的的实例生效。同样,单个对象可以通过动态的增加、修改和删除实例变量来进行修改。(这个我后来才知道的特性使得Python实现的对象比Smalltalk中的更灵活,后者局限在对象创建时指定的属性范围内)。

类语法开发

设计了用户定义类和实例的运行时表示之后,我的下一个任务是设计类定义的语法,特别是类包含的方法定义。设计时一条主要约束原则是我不希望给方法添加与函数不同的语法。重构语法和字节码解释器来处理如此相似的不同事物看起来是个艰巨任务。然而,即使在我成功的保持了语法一致之后,仍然需要找到处理实例变量的合适途径。开始,我想模拟C++中处理实例变量的隐含方式。例如,C++中,类定义代码如下:


class A {
public:
int x;
void spam(int y) {
printf("%d %d\n", x, y);
}
};


在类中,实例变量x被声明,在方法中,对x的引用到实例变量是通过隐含方式的。例如在spam() 方法中,变量x既不是函数参数,也不是局部变量,又在类中声明为实例变量的情况下,很简单x就是该实例变量。虽然我也想提供类似的方法,很快就发现对于没有变量声明的语言不存在这样的方法,因为无法简洁的区分实例变量和局部变量。

虽然理论上得到一个实例变量的值很容易。Python已有的变量名查找顺序是:局部、全局和内置。每个都表示为变量名和值映射的字典。因此,查找任一变量名就是一系列的字典搜索,直到找到第一个匹配成功。例如,当运行一个包含局部变量p和全局变量q的函数时,语句“print p, q”首先查找p,并在搜索顺序的第一个字典(包含局部变量)找到。接下来,在第一个字典中查找q,找不到,然后在第二个字典(全局变量)中继续查找,并找到q。

把当前对象实例字典放在方法运行时搜索顺序的最前面是很容易实现的。这时,假设一个对象的实例方法包含实例变量x和局部变量y,语句“print x, y”将会在实例字典找到x(搜索路径的第一个字典),在局部变量字典找到y(第二个字典)。

这种策略的问题是给实例变量赋值时就完蛋了。Python赋值时并不在多个字典中查找变量名,而是简单的对搜索顺序中的一个字典进行添加或修改变量,原来情况下这通常是局部变量。结果就是变量总是缺省创建在局部(local scope)范围。(当然,这儿也要说明的是,对每个函数中的每个变量可以用全局声明“global declaration”方式来重载顺序,使全局在最前。)

不改变赋值方法的简单策略之前,把实例字典置于搜索顺序第一项使得在方法中无法给局部变量赋值。例如,如下一个方法


def spam(y):
x = 1
y = 2


对x和y的赋值语句将会改写x的值,创建一个新的y并遮蔽局部变量y。交换实例变量和局部变量在搜索中的顺序,也只是将问题转进到另外一个方面而已,又无法给实例变量赋值了。

如果修改赋值的语义,对已存在实例变量名的赋值时修改实例变量,对不存在实例变量的情况时生成局部变量,也不是一个正确策略,因为这样会带来一个自举问题:实例变量如何被初始化呢。一个可能的解决方案是对实例变量进行清晰的声明,就像全局变量那样,但是我实在不愿意增加增加这样的特性,因为已经在完全不需要变量声明的路上走的太远了。而且,对表示全局变量的额外处理只是一个特殊情况,这种处理也表明在代码中使用全局变量时应谨慎。然而对实例变量的额外处理则几乎在类里面随处可见。另一个思路是实例变量有变量名上的区别。例如,以特殊符号开头(Ruby用@开头),或者采用前缀后缀等特殊的命名规范。这些都不能让我满意(直到现在我也没有改变过看法)。

最后,我决定放弃采用隐含方式表示实例变量的想法。C++这样的语言允许你用this->foo来明确表示foo是实例变量(当存在另一个局部变量foo时)。因此,我决定用明确声明方式作为指定实例变量的唯一方式。另外,与其用一个特定关键字来表示当前对象(“this”),我更愿意用方法的第一个参数“this”(或者其它等价变量名)。实例变量就可以总是通过这个参数的属性来引用了。
有了清晰引用,就不用为方法设计特殊语法了,你也不用担心变量名查找时的会变得很复杂。相反,只需要函数的第一个参数对应实例(通常用“self”),就可以成为一个方法了。如下代码:


def spam(self,y):
print self.x, y


我在Modula-3中学过类似做法,之前Modula-3中已经提供了import和异常处理的语法。Modula-3没有类,但是它允许你创建记录类型(record type)时包含完整类型函数指针成员并就近初始化,并且增加了语法糖,当x是当前记录类型的一个变量,m是一个该类型的函数指针,用函数 f初始化时,调用x.m(args)和f(x, args)是等价的。这正好可以应用到典型的对象和方法实现,使得实例变量等价于第一个参数的属性。

Python类语法的剩余细节也遵从这个规则或者其他实现引入的约束。出于简化的目的,我假定类语句由一系列方法定义组成,方法定义的语法和函数定义相同除了依照惯例要求第一个参数名为“self”。另外,与其给特殊的类方法(例如构造函数和析构函数)设计新的语法,我决定这个特征可由用户通过实现有特定命名约定的方法来完成,例如__init__,__del__等。这个命名规则从C语言中借鉴而来,C语言中由下划线开头的标识符保留给编译器使用,并通常有特殊用途(例如,C预编译器的__FILE__宏)。

这样,我设想的类定义代码就是下面这个样子了:


class A:
def __init__(self,x):
self.x = x
def spam(self,y):
print self.x, y


如同往常,我希望能尽量多的重复利用早期已有代码。以前一个函数定义就是一条可执行语句,简单的在当前命名空间设定一个变量,变量值就是函数对象(变量名就是函数名)。因此,与其设计完全不同的新方法来处理类,对我来说不如简单的把类的内容(class body)当做在一个新的命名空间运行的一系列语句。这个命名空间的字典将会被用以初始化类字典并创建一个类对象。实质就是把类内容用一个特殊方法当做匿名函数,并将执行后的局部变量组成的字典当做结果返回。这个字典传递给一个辅助函数(helper function)来生成类对象。最终,这个类对象存为相应命名空间下的一个变量。用户在知晓任何有效的Python语句都可以出现在类中后常感到惊奇。其实这个特点确实只是我想保持语法尽量简单的同时又不愿人工干涉来约束潜在可能的自然结果了。

最后一点细节是关于类实例化(实例创建)的语法。许多语言,例如C++和Java,采用特定的运算符“new”来生成新的类实例。C++中这么做或许讲得过去,毕竟类名在其解析器中有特殊地位,在Python中情况就不同了。我分析后很快得出结论,既然Python的解析器不在意调用的对象是何种类型,让类对象本身可以被调用是恰当的,是“最简”方案,也是不需引入新语法的好点子。这儿我可能有所超前--现在,厂函数(factory function)是创建是通常实例创建的优先方式,我所做的正是简单的把每个类的实例创建丢给它自己的厂。

特殊方法

如上节中已经简单提到的那样,我的目标之一是保持类的实现简单化。大多数面向对象编程中都有专为类设计的特殊的运算符和方法。例如,C++中,定义构造函数和析构函数有特殊的语法,与一般普通的函数和方法不同。

我显然不愿意为对象的特殊操作引入新的语法。相反,我解决这个问题的方法是简单的将特殊操作映射到一套已预定义好的“特殊方法(special method)”名,例如__init__和__del__。通过定义这些名称的方法,用户就可以提供对象的构建函数和析构函数所需代码了。

我还将这个设计应用到允许用户自定义类重定义Python运算符行为上来。前面已经提到,Python用C语言实现,利用函数指针表来实现各种内置对象能力(例如“get attribute”、“and”和“call”)。为了允许用户自定义类也拥有这些能力,我将这些函数指针映射到特别方法名上,如__getattr__、__add__和__call__。在实现C语言新建Python对象时已经存在一个从这些名字到function pointer表之间的直接对应。

__________
(*) 后来,new-style class需要控制对__dict__的改动;你仍然可以动态修改类,但是必须通过属性赋值方式,而不是直接操作__dict__了。

Python选择了动态类型

英文原文链接:http://python-history.blogspot.com/2009/02/pythons-use-of-dynamic-typing.html

原文作者:Guido van Rossum

ABC和Python的重要区别之一在于类型系统的通用风格。ABC是静态类型语言,ABC的编译器会分析程序中类型的一致性。如果存在不一致情况,程序就会被拒绝运行。和现在大多数静态类型语言不同的是,ABC使用类型推断(这一点和Haskell一样)而不是常在C语言中看到的那样明确声明变量类型。相反,Python采用动态类型。Python编译器对程序中的类型一无所知,统统放行,所有的类型检查在运行时完成。

虽然表面上看起来是对ABC背离很远,实际上并没有想象的那么夸张。和其它静态类型语言不同的是,ABC不(真的不?这个记忆可老有历史了:-)完全依赖仅靠静态类型检查以保证程序不会崩溃,还有一个运行时库在程序运行时对所有的操作类型进行再次检查。这个是作为编译器类型检查的安全性检查的一部分,在语言原型实现阶段没有完全实现。这个运行时库对于调试也有用,因为进行明确的运行时类型检查有助于输出更有针对性的出错信息(这是对实现团队来说的),而不是解释器在运行时不检查参数类型是否正确就盲目运行而导致内核崩溃那样。

然而,ABC在静态类型检查之外还采用运行时类型检查的最关键原因在于ABC是交互式的。在一次交互过程中,用户输入ABC程序语句和变量定义后立刻被执行。交互模式中一次完整过程可能将一个变量定义为数值类型,删除变量,然后重新定义变量(也就是创建另外一个同名变量)为字符串类型。在单个语句中某变量名从数值类型变为字符串类型会产生静态类型错误,然而在交互模式下不同语句之间也要求这种类型检查就有些不妥当了,否则不小心创建了一个数值类型的变量x,就会被禁止创建变量名为x的其他类型!因此作为折中,ABC对全局变量使用动态类型检查,对局部变量使用静态类型检查。为了简化实现,动态类型检查对局部变量也同样适用。

于是,从ABC的类型检查实现方法到Python的方法只剩下一步之遥了--Python简单的把编译时类型检查完全去掉。这完全符合Python的“偷工减料”原则,只要这种实现上的简化不影响最终的安全性,所有的类型检查都会在运行时进行所以各种类型错误会被捕获而不是导致Python解释器失效。

然而,一旦决定采用动态类型,就没有回头路了。ABC仔细设计了built-in(内置)运算以便从运算形式上推测参数类型。例如,从表达式“x^y”中,编译器可以得知变量x和y是字符串。Python中就无法采用这样的推理规则了。例如表达式“x+y”可能是字符串连接,也可能是数字相加,或者用户自定义类的重载操作。

2009年4月27日星期一

初创时期的语言设计与开发

英文原文链接:http://python-history.blogspot.com/2009/02/early-language-design-and-development.html

原文作者:Guido van Rossum

从ABC到Python

Python最初就受到ABC的影响且影响最深,ABC是Lambert Meertens、Leo Geurts和CWI其他人在1980年代初设计的编程语言。ABC的目的是成为一门教学语言、BASIC语言的替代者和个人计算机上的编程语言环境。ABC设计时先进行编程工作的任务分析,然后在包括严格用户测试的情况下再重复往返数遍。我个人在ABC开发组中的角色主要是语言及其集成编程环境实现。

Python中采用缩进的思想直接来自ABC,但是这个想法并不是ABC首创,缩进方式早已为Donald Knuth倡导并成为一种广泛的优良编程风格。(occam语言中也应用了缩进。)当然,ABC的作者确实发明了用冒号“:”来引导随后缩进块。在早期不使用冒号时的用户测试中,发现对于初次学习编程的新手来说,缩进的含义不够明确。添加冒号能明确缩进的含义:冒号提醒随后的程序块将要增加缩进,也将前后程序恰当的连接起来。

Python的主要数据类型也是在ABC的基础上修改而来。ABC的list实际上是bag或者说multiset,它利用修改过的B-tree实现,总是保持顺序。ABC的table则是总能保持key顺序的关联数组。我发现没有任何一种已有的数据类型适合表示从文件中逐行顺序读取内容,而我认为这是一种常见程序应用情况。(在ABC中勉强可以用行号为key的table来表示相应内容,但涉及行增删时处理困难。)于是我把list类型改为一种可有效处理增删的灵活数组,给用户完全控制list中元素顺序的能力。并提供了sort方法以备list需要排序时使用。

我还将保持key顺序的table改为hash表实现。我选择hash表的原因是因为我觉得它比ABC的B-tree速度更快也更容易实现。后者在理论上可以证明对于相当多的操作在时间和空间上是渐进优化的,然而实际情况是由于B-tree算法本身的复杂而难以正确实现。同样原因,对于规模较小的table,其性能也不出色。

我照搬了ABC的不可变tuple类型--Python中tuple的打包与解包操作直接从ABC中借来。由于tuple内部由数组实现,我决定给tuple加上数组常有的index(索引)和slice(切片)操作。

在给tuple增加数组操作接口时我发现需要解决tuple的边界情况,即tuple含有元素数目为0或者1的情况。我从ABC中引入的规则之一便是,对任一数据类型进行打印或转换为字符串时,其输出应当恰是语言语法分析器可有效处理的输入。所以,我需要有包含元素长度为0或者1的tuple的标记方法。同时我也不想失去单元素tuple和普通用括号包含表达式之间的区分能力,于是我采用了有些丑但却实用的方法,就是在括号中的表达式后附加一个多余的逗号,就变为单元素的tuple,用“()”来表示空tuple。需要说明的是,对于Python语法而言,tuple的括号并不是必须的,除了表示空元素这一情况时--我觉得用“空”表示空tuple太容易掩盖相应的书写错误了。

Python的string和ABC中的不可变string语义十分相近,但是有一点标记上的区别,Python采用从0起始的索引。现在已经有了三种可以索引的类型,list、tuple和string,我决定将其归纳为sequence作为他们的通用概念。以使对任意sequence类型的核心操作形式相同,包括获取长度(len(s)),索引(s[i]),切片(s[i:j]),和遍历(for i in s)等。

数值类型是我背离ABC设计思路最远的部分之一。ABC在运行时有两种类型的数值类型:可表示任意精度有理数的精确数和增大表示范围的二进制浮点数所表示的近似数。我没有采用前者那种有理数表示方式。(轶事:有次我想用ABC来计算自己要交的税。看起来很正常的程序,计算几个简单数字却费了很长时间还没算出来。又仔细看了下才发现正在处理几千位数字精度的算数运行,全然不顾最终输出结果只要精巧到荷兰盾的元和分即可。) 对于Python,我因此选择了更传统的方式,采用机器整数和机器二进制浮点数来表示。Python的实现中,这两种数据类型简单对应了C数据类型中的long和double。

考虑到对无上界精确数的需求也很有实际意义,我增加了一个大数类型,称为long。我当时已经有了一个大数类型,是我几年前为了改进ABC实现时的半成品(ABC最初的相应实现,也是我对ABC做的最早贡献之一,是用十进制作为内部表示的)。因此,将代码拿来给Python用对我来说是再合适不过的事情了。

虽然我为Python添加了大数类型,这里一定要明确说明的是我并不希望所有的整数运算都使用大数类型。根据我和CWI的同事对Python程序的profiling结果,我知道整数运算占据了大多数程序整个运行时间的相当比例。显然,整数的最常用情况是对内存可容纳的sequence类型进行索引操作。因此,我预测机器整数对各种常见情况是实用的,只有在做“严肃的数学”或者用分来计算美国国债时才使用大数类型。

数值类型的问题

对数值类型的实现,特别是整数类型,是我犯了严重设计错误之处,也让我在如何设计Python方面获得了经验教训。

既然Python有两种整数类型,我需要在程序中区分两种类型的方法。我的解决方式是当用户需要long类型时,自行对数字添加L后缀(例如1234L)。用户对不必要的实现细节无需操心是ABC的设计哲学,这里是Python违反这一思想的地方之一。

悲哀的是,这还只是一个更大严重问题的冰山一角。一个相当夸张的错误是在特定情况下,integer和long两种整数实现会有语义上的细微不同!因为int类型是由机器整数表示,向上溢出时结果只是简单的截断为低32位或其它某个C中long类型对应的精度。此外,int类型通常情况下是有符号数,在位操作、位移操作、和8进制/16进制互相转换时则当做无符号数。而相对应的,long类型则总是有符号数。因此,某些操作会因为参数是由int还是long类型表达而产生不同的结果。例如,在32位运算中,1<<31(1左移31位)是一个32位的大负数,而1<<32结果为0。然而1L<<31(long类型的1左移31位)产生一个long类型整数,值为2**31,1L<<32的结果为2**32。

为了解决这个问题,我做了一些简单的修补。对以前简单截断结果的操作做出变更,我将大多数算术运算结果超过存储范围时改为抛出溢出异常(raise an OverflowError exception)。(唯一的例外是上文提到的“位运算”操作,我认为用户期望这儿的结果和C中对应运算保持一致。)如果没有这个检查,Python用户注定在书写代码时依赖一个以2**32为模的有符号二进制运算(正如C中那样),修正这个错误对社区的转换来说着实是一件苦事。

虽然包含溢出检查看起来只是实现上一个细微末节,然而调试时的痛苦经历让我认识到这是一个有用的特征。作为我早期Python编程经验之一,我尝试实现了计算“Meertens数”的简单数学算法,这个消遣数学算法是由Richard Bird为纪念ABC首席发明人在CWI满25年时发明的。最前面几个Meertens比较小,但是算法转化为代码时,我当时还不知道中间计算结果远大于32位。这占用我大量时间和精力才找到问题根源,于是我立刻决定通过检查所有整数的运算来解决问题,只要结果不能用C语言中long表示就抛出异常。溢出检查的额外耗费由于我在实现运算结果需要生成新的对象产生较大耗费而显得影响有限。

悲哀的是,我还要继续道歉,抛出异常仍然不是正确的解决方案!当时我受C语言的“T数值类型上运算返回结果也是T数值类型”这一规则约束。这条规则也是导致我在整数语义上另一个重大错误的原因:截断整数除法的结果,这个我在后面的博文中会谈到。放个马后炮,我应该让溢出的int整数操作结果升级为long类型。这也是今天Python采用的方式,可惜这个转换用了太长时间。

尽管在数值设计方面遇到这些问题,这个经历还是产生了一个十分有益的结果。我决定Python中应当不含有未定义的结果--相反,当计算结果不正确时,总是抛出异常。这样Python程序员就不会因为运算中内部的悄悄转换而遭遇失败。这是一个重要语言设计原则,无论对语言本身还是标准库来说都是如此。

微软1996年发布的软件就含有Python代码

英文原文链接:http://python-history.blogspot.com/2009/01/microsoft-ships-python-code-in-1996.html

原文作者:Greg Stein

衷心感谢Guido给我这个机会,能在这里和大家分析我的Python经历!

我自己接触Python的经历先放一边,有机会另开新贴,要说的其结果就是1991年我和其他几个人合伙成立了一个新公司。我们设计了一个大的客户端/服务器端系统来处理B2C电子商务。当时用的旧X.25网络之上定制的TCP协议,陈旧的技术了。

到了1995年,我们认识到,和最初设想的相反,很多消费者都是Internet用户,我们需要一个基于Internet的系统,以使我们的客户(那些货物供应商贩)能推销产品给Internet上的消费者。我当时任务就是找思路,我采用Python作为原型开发工具。
我们在移植到完全基于浏览器的解决方案时碰到了问题。我们的原有客户端已不再适用,我们需要帮助消费者建立新的消费体验,而且要实现相应的服务端。当时通过浏览器进行交互就意味着给Apacher和Netscape HTTP服务器写CGI脚本。我利用CGI来连接现存的服务器后台,处理订单、更新购物篮、获取商品信息等。这些CGI脚本的处理结果都是纯粹的Html网页,(1995年还没有AJAX啦!)。
这个方法不太理想,因为每次请求都会产生一个新的CGI进程。响应性能十分受限。到了1995年12月,去华盛顿特区参加Python研讨会时,我接触到一些可以在服务进程中持续运行的Apacher和Netscape模块(由Digital Creations实现,这个公司另一个广为人知的名字是Zope)。这些模块通过名为ILU的RPC系统与后台长期运行进程进行通讯。利用这个系统CGI频繁生成新进程的开销得以避免,网店购物经历也变得有趣起来!我们着手将原型系统转换为生产用代码。我们越用越越顺手,更多人参与到项目中来。接下来几个月的开发进展神速(感谢Python!)。

1996年1月,微软找上门来。微软内部关于电子商务系统的开发正举步维艰。他们需要熟悉这个行当的明白人(那个时候我们已经在电子商务领域耕耘了好几年了)。春天谈判进行的过程中,我们继续进行软件开发,收购最终在1996年6月完成。
我们带着自己的一小摞Python代码来到微软,要处理的便是让产品在Windows NT平台上运行。我们加入的团队有丰富的Windows经验,构建IIS插件通过命名管道和后台服务之间的通讯。从七月份开始进入火速开发,1996年10月,我们发布了微软商店服务软件1.0版。
于是,如果你仔细观察内部,就能看到,有些隐藏起来的东西,例如一个Python解释器,一些模块的动态链接库,一些.pyc文件。微软显然不愿对这件事情大张旗鼓,不过熟悉的人就会找到的。

2009年4月26日星期日

个人历史 第二部分 CNRI及其之后

英文原文链接:http://python-history.blogspot.com/2009/01/personal-history-part-2-cnri-and-beyond.html

原文作者:Guido van Rossum

在Python研讨会(详情参见前帖)上我得到去CNRI从事移动代理方面的工作机会,CNRI全称是美国全国研究创新联合会,是位于弗吉尼亚州瑞斯顿的一个非盈利研究实验室。我于1995年4月正式加入。CNRI主管Bob Kahn第一个告诉我Python和Lisp除了在表面的(语法形式上)区别很大之外是如此相似。在CNRI Python通过DARPA对移动代理研究的支持而得到间接资助。虽然DARPA支持了应用Python的项目,对Python语言本身开发的直接支持并不多。

在CNRI,我领导并参与招聘了一个纯粹使用Python实现移动代理的开发小组。最初成员包括Roger Masse和Barry Warsaw,他们是在NIST召开的Python研讨会上对这门语言着魔的。除此之外我们还招聘了Python社区的Ken Manheimer和Fred Drake。Jeremy Hylton从MIT毕业后本来从事文本信息检索工作,也加入到我们组。开发组最初由Ted Strollo管理,后来是Al Vezza。

开发组辅助我创建和维护Python社区的新配套实施,如Python.org网站、CVS服务器、Python各专业兴趣组(SIG)邮件列表。在CNRI Python发布了从1.3到1.6版本。其中的1.52版本在很多年里是最流行和稳定的版本。

GNU mailman也诞生于此地:我们最初利用的是一个名为Majordomo的Perl工具,但是Ken Manheimer感到难以维护,也想找寻一个基于Python的实现方案。他找到John Viega利用Python写的部分代码,并接手了维护。Ken离开CNRI去Digital Creations之后,Barry Warsaw接手了维护,并且说服自由软件基金会采用mailman作为它们的官方邮件列表工具。为此Barry以GPL协议(GNU通用公共许可证)发布mailman软件。

Python研讨会继续进行,最初是一年两次,后来由于规模变大,筹办事务繁重而改为一年一次。由最先想主办的机构来组织,NIST组织了第一届,USGS组织了第二届和第三届,LLNL组织了第四届,并且从此改为一年一次。最终CNRI把持了主办权,并随后(联合WWW以及IETF会议)作为一个商业活动的副产品,称作Fortec。参会者迅速飞涨到数百人。当我离开CNRI一段时间后Fortec逐渐衰落。国际Python会议成为O'Reilly开源大会(OSCON)的一部分,与此同时Python软件基金会(有关PSF详情参见下文)开始了一个面向草根大众的PyCon的新会议。

在CNRI时,我们还创办了第一个(松散的)Python组织。在Mike McLay和Paul Everitt的努力下,成立了“Python基金会”这个基金会还在规划阶段便随风而逝了。Bob Kahn 建议成立“PSA:Python软件部门”,这不是一个独立的法人,而是作为CNRI(非盈利)法人之下的一个群体活动组。PSA成功的把一大群Python用户集合在一起,不过PSA的效力也因缺乏独立法人身份而受限。

CNRI还是用DARPA的资金资助了JPython的开发(后来缩写为Jython),这是一个用Java实现并为Java所用的Python实现。Jim Hugunin在MIT读研究生期间创建了该项目。他说服了CNRI雇用自己继续完成Jython(也可能是CNRI主动说服Jim加入进来 -- 当时我正在渡假。)。不到两年后Jim离开了CNRI,去了施乐帕洛阿尔托研究中心的AspectJ项目,Barry Warsaw接手了Jython并继续开发。(很久以后,Jim又开发了IronPython,这是在微软.NET平台上的Python实现。Jim还是Numeric Python第一个版本的作者。)

其他CNRI的项目也开始使用Python。有几个新的Python核心开发人员便从中产生,特别有Andrew Kuchling、Neil Schemenauer和Greg Ward,他们当时为微机电系统交换项目工作。(Andrew早在加入CNRI之前就开始为Python语言做出贡献;他第一个主要项目是Python密码学工具包,这个第三方库使得Python用户得以使用许多基本的密码学算法。)

看到Python日益取得的影响,CNRI尝试建立一种更加直接资助Python的模型,而不是像以前通过DARPA的研究基金那样的间接方式。我们基于X协会模式建立了Python协会,最低入会费为2万美元。然而除了HP公司的一个研究组,我们并没有得到过多关注,最终因为缺乏收入来源而停止运作。另一次尝试则是通过建立CP4E(人人可编程)计划来得到资助,这个计划得到了一些DARPA资金。然而不足以维持整个团队运作,而且看起来那几年就是通过一些同学关系网得到花费资金。这并不是我所希望的,于是我开始找寻其它可能。

最终到了2000年早期,互联网泡沫时代的到来了(至尽仍未消失殆尽)。让我和CNRI其他三个成员(Barry Warsaw,Jeremy Hylton和Fred Drake)跳槽到BeOpen.com,这是加利福尼亚一家招聘开源开发人员的新创公司。Tim Peters,Python社区的关键一员也在这时加入我们。

预计转移到BeOpen.com之后的需要面临一个Python将来的所有权问题。CNRI坚持更改协议并且要求我们采用新协议发布Python1.6版本。最初我在CWI时用的旧就协议是一个MIT协议版本,而在CNRI之前发布的Python版本修改甚微,主要是增加了个条CNRI的免责条款。1.6版本的协议则是由CNRI律师起草的冗长公文了。

我们和FSF(自由软件基金会)的Richard Stallman和Eben Moglen就新协议问题进行了多次讨论。他们担心新协议有可能和GPL不兼容进而威胁GNU mailman的合法性。而现在GNU mailman已经是FSF离不开的重要工具了。 在Eric Raymond的帮助下,CNRI Python协议的更动之处除了行文不易于理解之外让FSF和CNRI双方都满意。我唯一能从中想到的好处(再次感谢Eric Raymond的帮助)是Python被FSF亲口认可为开源许可协议。后来又对协议做了细微改动以反映两个Python版权的继承者,BeOpen.com,然后是Python软件基金会。 当然CNRI律师的大作依然有效。

正如那时候许多初创公司一样,BeOpen.com的商业计划也在惨烈中收场。留下了大量债务,一些对部分公司管理人员所扮演角色的沉沉疑惑,已经一些包括我的团队在内的心灰意冷程序员。
我的团队,现在叫PythonLabs了,碰到了好年头,相当抢手,整体被Digital Creations招聘为一个部门。这是最早应用Python开发的公司之一(Ken Manheimer比我们还要早几年。)Digital Creations很快更名为Zope公司,这是由于他发布了自己的主要产品,开源web内容管理系统Zope。Zope的创办人Paul Everitt和Rob Page,以及CTO Jim Fulton参加了1994年在NIST召开的最早的那届Python研讨会。

历史差点改写:除了Digital Creations,我们当时还考虑了VA Linux和ActiveState伸出的橄榄枝。VA Linux当时是股票市场的后起之秀,但是最终它的股价(使得Eric Raymond一度成为纸上的大富翁。)还是戏剧性的崩溃了。现在来看ActiveState如果不是在加拿大的话或许是个不错的选择,尽管创办人Dick Hardt的个性有争议。

2001我们创建了Python软件基金会,这是一个非盈利组织,最初的成员主要来自当时Python开发的主力贡献人员。Eric Raymond是创办人之一。我下次再多写点这个事情。

2009年4月25日星期六

个人历史 第一部分 在CWI的日子

英文原文链接:http://python-history.blogspot.com/2009/01/personal-history-part-1-cwi.html

原文作者:Guido van Rossum

Python最早诞生在阿姆斯特丹一个叫CWI的研究机构,CWI是一个荷兰机构名称的缩略语,翻译过来意思就是数学和计算机科学中心。CWI是个有意思的地方,由荷兰教育部提供资金,也接受其他研究基金资助,负责管理计算机科学和数学学科在学术方面的研究。无论何时,在CWI都能看到大量的博士生在闲逛,也能看到还记得单位原来名称“数学中心”的行内老前辈。最令CWI名声在外的事情当属这儿发明的编程语言 Algol 68了。

我在CWI的工作开始于1982年下半年,当时刚从大学毕业,在Lambert Meertens Steven Pemberton负责的ABC组做程序员。四五年后,ABC由于反响不太理想而项目中指终止,我去了由Sape Mullender负责的CWI Amoeba组。Amoeba组做的是CWI和阿姆斯特丹自由大学联合开发的基于微内核的分布式系统, 由Andrew Tanenbaum负责。1991年Sape离开CWI,去Twente大学做教授。我去了新成立的由Dick Bulterman负责的多媒体组。

Python是我在CWI工作时的直接成果。正如我后面要提到的那样,ABC给了我开发Python的关键启发,Amoeba则是开发Python的直接诱因,而多媒体组则是Python成长的摇篮。然而至少据我所知,CWI没有正式资助过Python开发。Python做过Amoeba和多媒体两个组的重要工具而得以成长,CWI是这一过程的见证者。

我最初设计Python的动机是感觉Amoeba项目缺少一门高级语言。我发现用C开发系统工具过于耗时。而使用Bourne shell 也有一些不妥之处。其中最重要一点就是作为新设计思路下的微内核分布式系统,Amoeba的基本操作与Bourne shell所适用的传统操作系统的基本操作相去甚远(前者改进明显)。因此,需要一门语言来“消除C和shell之间的隔阂”。Python有很长一段时间用把这个当做卖点。

看到这儿,你或许会问“为什么不改进一门已有的语言呢?”在我看来,当时并没有太多合适的语言可选。我对Perl 3比较熟悉,但是Perl和Unix的结合比起Bourne shell来还紧密。我也不喜欢Perl的语法,我对语法风格的偏好受以前所学语言影响很深,如Algol 60、Pascal和Algol 68,还有最后学习也同样重要的ABC,我曾为之付出四年的时光。所以,我决定设计一门自己的语言,尽量从ABC中借鉴一切我喜欢的特性,同时修正我认为ABC存在的问题。

我首先想修改的就是名字!碰巧,ABC团队在为自己所开发开发语言选择名字的时候遇到了一些麻烦。最初选定的名字B未获通过,是因为已经有一门更早更为人所知的语言用了B这个名字。另外B也只是作为暂定名(有个笑话说B是包含了语言名字的变量名,所以用斜体)。团队公开征集新名字,可惜参赛方案无一满意,内部提案最终中选。这个名字想表达的意思是说这门语言使得编程像学字母ABC一样简单,但是这些说法从未能让我满意。

因此,与其在起名问题无休无止的耗下去,我决定不再纠缠。我就选了首先想到的事物,也就是Monty Python’s Flying Circus,我最喜欢的戏剧团之一。对这个实质上的“小私活”来说,这个称呼有些风马牛不相及。“Python”易记、干脆,符合当时采用名人对编程语言命名的传统,如Pascal,Ada,和Eiffel。Monty Python马戏团虽不在科技方面见长,去也为相当多极客喜爱。这个命名也符合CWI Amoeba组用电视剧命名程序的传统。

很长一段时间,我都拒绝把该语言和蛇联系起来的企图。最终,当O’Reilly准备在他们出版的第一本Python书籍《Programming Python》封面上放一条蛇的时候,我做了让步。用动物做封面是O’Reilly的传统,既然非要用一个动物,我想是一条蛇也好。

名字定了之后,我在1989年12月份开始动手实现Python。在1990年的头几个月就有了一个可工作版本。我没有当时的记录,不过我清楚的记得我为Python实现所写的第一部分代码是一个简单的LL(1)剖析器产生器,我称之为“pgen。”。这个剖析器产生器仍是Python源代码的一部分,而且可能是所有Python源代码中改动最少的部分。这个Python的最早版本在1990年有几个CWI的用户,主要但不全是Amoeba组的。除我之外的关键开发人员是我的同事程序员Sjoerd Mullender(Sape的弟弟)和Jack Jansen(他在我离开CWI多年之后仍是负责Macintosh平台Python移植的领头开发人员之一)。

1991年2月20号,我第一次在alt.sources新闻组向外界发布了Python(21个uuencoded打包文件,要先合并在一起然后uudecoded解包为一个压缩的tar文件。)。这就是0.9.0版,发布采用的协议基本上照抄当时一个X11项目采用的MIT协议,只是改单位名称为“Stichting Mathematisch Centrum”,这是CWI的上级单位,以作为法律实体。就这样,如同我写过的所有代码那样,Python是开源软件,这要比Eric RaymondBruce Perens在1997年提出“开源软件”这个术语还早了。

立刻就收到许多反馈,在这种鼓励下我在接下来的几年一直持续的发布新版本。我开始使用CVS来跟踪代码变更和便于同Sjoerd、Jack交换代码。(恰巧,CVS最初是由Dick Grune设计的一套shell脚本组成,他是ABC组的一个早期成员。)我写了个一个FAQ,然后正如在有web之前时代FAQ的处理惯例那样周期性的张贴到一些新闻组,建立了一个邮件列表。comp.lang.python新闻组成立于1993年3月,此事我有鼓励但未亲自参与建立。新闻组和邮件列表通过一个双向网关连接起来。该双向网关至今仍工作,不过具体实现已是mailman的一个特性。mailman是开源邮件列表管理软件的首选,由Python语言实现。

1994年夏天,新闻组因为一个“如果Guido被公车撞了怎么办?”的线索而人声鼎沸。这个线索关注的是日益成长壮大的Python社区对我个人贡献的依赖关系问题。讨论在Michael McLay邀请我到NIST做两个月访问学者时达到顶点。 NIST是美国国家标准技术研究所,前身为国家标准局。位于马里兰州的盖瑟斯堡。Michael 有些NIST的“客人”对使用Python做一些和标准有关的项目感兴趣,也有预算资助我留下来,目的是帮助他们提高Python使用水平,也可以说是改进Python以符合他们的需求。

第一届Python研讨会于我呆在NIST的1994年11月召开,这要感谢NIST程序员Ken Manheimer提供的重要帮助与支持。大约有20个参会者,约半数人仍活跃在Python社区,有些人已经成为主要开源项目的领军人物(如Zope项目的Jim FultonGNU mailman项目的Barry Warsaw)。在NIST的支持下,我还在计算机系统协会(Usenix)的小语言会议上做主题发言,约有400人参会。会议在圣达菲召开,由Tom Christiansen组织,他是一个思想开明的Perl拥护者,在他的引荐下我见到了Perl的发明人Larry Wall和Tcl/TK的作者John Ousterhout

下集预告:我怎么在美国找到工作的……

Python 时间表

英文原文链接:http://python-history.blogspot.com/2009/01/brief-timeline-of-python.html


原文作者:Guido van Rossum


Python开发过程也正是其它动态(开源)编程语言如Tcl、Perl和(晚的多的)Ruby活跃开发逐渐流行的时期。为了从远景对Python进行观察,窥其全貌,如下时间表列出了Python各个版本的发布日期。由于我没有持续记录,早期版本的发布日期并不精确。


发布日期 版本


December, 1989 开始着手实现


1990 CWI内部发布


February 20, 1991 0.9.0 (发布到alt.sources)


February, 1991 0.9.1


Autumn, 1991 0.9.2


December 24, 1991 0.9.4


January 2, 1992 0.9.5 (仅限Macintosh平台)


April 6, 1992 0.9.6


Unknown, 1992 0.9.7beta


January 9, 1993 0.9.8


July 29, 1993 0.9.9


January 26, 1994 1.0.0


February 15, 1994 1.0.2


May 4, 1994 1.0.3


July 14, 1994 1.0.4


October 11, 1994 1.1


November 10, 1994 1.1.1


April 13, 1995 1.2


October 13, 1995 1.3


October 25, 1996 1.4


January 3, 1998 1.5


October 31, 1998 1.5.1


April 13, 1999 1.5.2


September 5, 2000 1.6


October 16, 2000 2.0


April 17, 2001 2.1


December 21, 2001 2.2


July 29, 2003 2.3


November 30, 2004 2.4


September 16, 2006 2.5


October 1, 2008 2.6


December 3, 2008 3.0


我给那些在python.org现在还有的版本添加了对应链接。这里要提及的是许多版本发布之后还会有随后的若干个小版本,例如2.0.1;限于篇幅我没有将这些小版本也包含进来。最初阶段的源代码任何可以从如下地址访问: http://www.python.org/ftp/python/src/ 。一些早期版本的二进制文件和其它文物也可以到该链接的上一层进行找寻。

2009年4月24日星期五

Python设计哲学

英文原文链接:http://python-history.blogspot.com/2009/01/pythons-design-philosophy.html

原文作者:Guido van Rossum

随后的博文重心落在Python历史细节上。然后,在开始具体内容之前,我想在这里详细说明一下Python设计与实现过程中指导我做出决策的哲学方针。

首先,Python最初只是一个人的“私活”,没有正式资金支持,我希望能尽快做出成品,以便向管理层要求对该项目的支持(这方面我做的相当成功)。基于这一点,采取了如下一些节约时间的原则:
  • 只要合适,尽可能从其它语言借用已有思路。
  • 事情应当简约而不简陋(爱因斯坦)。
  • 只做一件事,做好一件事(UNIX哲学)。
  • 对性能不需要太多顾虑,有在遇到性能瓶颈是进行优化的方案即可。
  • 不对抗大环境,顺势而为。
  • 不强求完美,因为通常“足够好”已经“足够好”了。
  • (因此)有些细碎之处可以先放在一边,尤其是那些放在以后正确处理的部分。

还有一些原则则不是从节约时间的角度来考虑的,有时还会因此耗费相当时间:

  • Python实现应当不依赖具体平台,可以允许部分函数不是在所有平台实现,但是核心部分应当在所有平台都正常工作。
  • 不劳烦用户处理那些机器可处理的部分(我并没有一直遵守这一原则,因此损失惨重,其中一些教训将在后文提到)。
  • 支持并且鼓励用户写平台无关代码,同时不禁止利用平台特性。(这一点和Java的设计原则尖锐对立。)
  • 一个大规模复杂系统应当有多层次的可扩展性。这对自己动手进行扩展的新老用户都有利。
  • 出错不应当是致命的。也就是说,只要虚拟机还能工作,用户代码就有从出错处恢复的能力。
  • 同时,也不应简单忽略出错之处。(这和上一条一起,导致了在具体实现时广泛采用异常。)
  • 用户所写Python代码中的bug不应使Python解释器产生未定义行为;永远不会因为用户代码中的错误导致Python核心错误。

最后,我还有些关于编程语言设计的好思路,这些主要是从ABC开发组借鉴而来。正是在ABC开发组我第一次有了编程语言设计与实现的实际经验。这些思路可归结为优美、简洁、良好可读性等主观想法,难以用言语具体表达。

虽然在后边我还会提到ABC对Python的影响,我想在这儿强调特别和可读性有关的一点:慎用标点符号,只采用书面英语和高中代数中的常用标点符号。当特定用法在编程语言中已经长期存在成为传统时,可以考虑破例。例如“x*y”表示乘法,“a[i]”表示数组下标,“x.foo”表示选择对象属性。但是Python不用“$”表示变量,也不用“!”表示运算以避免副作用。

Tim Peters,是Python的长期用户,并最终成为最高产与卓越的核心开发人员。他对我的设计语言进行理解并表达为“Python之禅”。我把全文贴出来:
(这一段的翻译译者ZoomQuiet,来源:http://wiki.woodpecker.org.cn/moin/PythonZenZoomq)

  • 美丽好过丑陋,
  • 浅显好过隐晦,
  • 简单好过复合,
  • 复合好过复杂,
  • 扁平好过嵌套,
  • 稀疏好过密集,
  • 可读性最重要,
  • 即使祭出实用性为理由,特例也不可违背这些规则.
  • 不应默认包容所有错误,得由人明确的让它闭嘴!
  • 面对太多的可能,不要尝试猜测;应该有一个(而且是唯一)直白的解决方法;
  • 当然,找到这个方法不是件容易的事~谁叫你不是荷兰人呢?
  • 但是!现在就做永远比不做要好;
  • 若实现方案很难解释,那么它就不是一个好方案;反之也成立!
  • 名称空间是个绝妙想法. --现在就来共同体验和增进这些吧!

虽然我在ABC的工作经验对Python影响深远,ABC开发组有一些设计原则和Python有根本不同。Python尽量避免了下面这些设计思路:

  • ABC开发组努力追求完美,例如采用了树的结构,这对于大的收集(collection)是渐进优化的(但是对小的收集效果并不理想)。
  • ABC开发组希望将用户和“笨重脏乱的计算机世界”尽量隔离开。不仅不设定数字上限、字符串长度、收集包含元素数量(仅有可用内存来进行限制),而且让用户不需要直接接触文件、磁盘、“存盘”等概念。ABC本身成为用户唯一需要了解的工具,基于这一思路,ABC开发组创建了一个ABC所特有的集成编辑环境(确实在使用ABC时存在不使用集成编辑环境的可能,但是这样做困难重重,多是求之不得之后的一种懊悔,而且只能间接实现。)
  • ABC开发组假定用户没有任何计算机知识(或者用户愿意忘记以前所学的计算机知识)。因此,他们设计了另外一套“新手友好”的计算机术语。例如,程序被改称“怎么办”,变量则称作“定位件”。
  • ABC开发组在设计ABC语言时对其未来发展的规划并不明确,也没有考虑用户如何参与对语言的设计。ABC是一个封闭的系统,被设计为尽量“完美无缺”。不鼓励用户查看ABC的具体实现。虽然在项目后期也有考虑对高级用户开放部分实现,但终未能实现。

从各方面来说,我在创建Python语言时采用的设计原则可能是Python语言获得巨大成功的最重要原因之一。不强求完美,而是让早期的用户群发现Python对他们的需求“足够好”。随着用户数量的增加,他们所提出的改进建议逐渐融入语言本身。我们可以在后面的介绍中看到,许多建议对语言有相当变化,需要对核心部分进行显著改动。时至今日,Python仍在继续进化过程之中。

介绍与概述

英文原文链接:http://python-history.blogspot.com/2009/01/introduction-and-overview.html
原文作者:Guido van Rossum

介绍

Python, 如同Perl、Tcl、PHP以及新进的Ruby语言那样,是最流行的动态编程语言之一。虽然常被称作“脚本”语言,Python是一门功能齐全的通用编程语言,就像Lisp和Smalltalk以及其他通用编程一样。发展到今日Python的应用已经无所不在,从随写随丢的简单脚本到24x7不间断大规模网络服务应用随处可见其身影。Python的应用场所包括GUI和数据库编程、网络客户端和服务器端编程 、程序测试。科学家在世界上运行最快的超级计算机上编写程序用到了Python,Python还是小朋友初次学习编程时的好帮手。

在这个Blog中,我将讨论重点放在Python的发展历史上,特别是如下方面:Python是如何开发的,设计时所受到的主要影响,所犯的错误和从中吸取的教训,以及Python语言未来的发展方向。



感谢: 我要感谢 Dave Beazley ,为这个Blog提供的许多优美语句。(有关这个Blog起因的更多信息,参考我另外Blog的一篇博文。)



Python鸟瞰



人们初次浏览Python程序时,往往对Python代码样式感到亲切,至少是初看之下,和其他传统编程语言C或者Pascal风格类似。这并非偶然,Python从C语言借用了大量语法,例如Python的许多关键字if, else, while, for和C一致,Python标识符遵守和C同样的的命名规则,大部分运算符号也和C中的含义相同。当然,Python显然不等同与C语言,一个主要的不同之处是Python来界定一组语句时不使用括号,而是缩进。例如,对于如下的C语句:


if (a < b) {
max = b;
} else {
max = a;
}


Python省略了两边的括号,并且省略了语句结尾的分号。书写为如下形式:


if a < b:
max = b
else:
max = a


Python与C为代表的编程语言另外一个主要区别在于Python的动态类型。在C语言中,变量必须被清晰的声明并指定类型,例如int类型或double类型。该信息在静态编译时可用于类型检查,并分配存储变量所需内存。在Python中,变量只是引用对象的名字,变量在赋值前不需提前声明,甚至在程序运行中变量类型还可以发生变化。如同其他动态语言那样,所有的类型检查是在运行时由解释器完成的,而非一个单独的编译步骤。

Python基本内置数据类型包括布尔类型、数值型(机器字整数、任意精度整数,实数和复数)、字符串(8bit以及Unicode类型)。这些基本类型是不可变的,也就是说这些类型的对象所包含的值在创建后不能被改变。组合内置数据类型有tuple(不可变数组)、list(可变数组)和dictionary(hash表)。

在程序结构方面,Python支持包(由模块和/或包组成)、模块(由相关代码组成的单个源代码文件)、类、方法和函数等多个级别。对于程序流控制,则有 if/else, while, 和一种对任意可迭代对象适用的高级for语句,对于出错处理,Python采用了(不可恢复的)异常机制。raise语句抛出异常,try/except/finally 语句处理异常。在遇到错误时,内置操作就抛出异常。

在Python中所有可以被命名的对象都可以被称作“一类公民”(“first class”)。这意味着函数、类、方法、模块以及其它可命名对象在运行时可被随意赋值给其它对象、被查看或者被放入各种数据结构中(例如list或者dictionary)。提到对象,还要说的是,Python全面支持了面向对象编程,包括用户自定义类、继承、运行时方法动态绑定等。

Python有丰富的标准库,这也是Python流行的主要原因之一。标准库超过了100多个模块且继续改进丰富中。这些模块包括了正则表达式匹配、标准数学函数、线程、操作系统接口、网络编程、标准互联网协议(HTTP、FTP、SMTP等)、email模块,XML处理、HTML解析以及一个GUI模块(Tcl/Tk)。

另外,还有极其丰富的第三方模块和包,其中大部分是开源的。有web开发框架(数目之多数不胜数),许多GUI开发工具箱、高效的数值计算库(流行的Fortran包也有Python封装),Oracle、MySQL等多种数据库的Python接口,SWIG是将各种C++库封装为Python模块的工具……还有许多优秀模块,不胜枚举。

Python的一个主要优势在于有些复杂的任务可以用很少几行代码轻松完成(这也是动态语言的共同优点)。以下为一个示例,这个简单的Python脚本抓取一个网页,分析内容,得到其中URL链接,并打印前10条。


#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# 扫描网页,获得其中的超链接

import re
import urllib

regex = re.compile(r'href="([^"]+)"')

def matcher(url, max=10):
"打印从当前URL获取内容中所包括的前面几个URL"
data = urllib.urlopen(url).read()
hits = regex.findall(data)
for hit in hits[:max]:
print urllib.basejoin(url, hit)

matcher("http://python.org")



这个程序很容易改进为Web爬虫,而且,Scott Hassan 确实告诉过我Google的第一个Web爬虫就是由他用Python写成的。现在Google有数以百万行计的代码用于管理运行的各个方面,从自动构建到广告管理。(声明:作者目前为Google员工。)

更近距离的继续观察,Python的实现是由字节码编译器和解释器组合而成的。模块被加载时,编译器缺省被调用,有几条Python语句还要求在运行时可以使用编译器。虽然Python的最主要实现是用C语言完成的,还存着其它几种正在流行的Python实现。Jython 是一个运行在JVM上可以和Java无缝连接的版本。IronPython是微软.NET平台上可以和其它.NET语言集成在一起的实现。PyPy 是一个由Python实现的优化Python编译器/解释器(PyPy是欧盟资助项目,目前仍处在研究阶段)。还有Stackless Python,这是一个C实现的变种,特点在于函数/方法调用时减少了对C堆栈的依赖,以实现协程, continuations,和微线程。