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”可能是字符串连接,也可能是数字相加,或者用户自定义类的重载操作。