The History of Python

显示标签为“Python History”的博文。显示所有博文
显示标签为“Python History”的博文。显示所有博文

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.

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

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

2010年6月24日星期四

The Inside Story on New-Style Classes

The Inside Story on New-Style Classes

新类内幕

英文原文链接: http://python-history.blogspot.com/2010/06/inside-story-on-new-style-classes.html

原文作者: Guido van Rossum

[提醒,本篇是长文,且多技术细节。]

表面看新类和原来的类实现十分相似,实际上新类还引入了一些新概念:
  • 低级构造方法__new__()
  • descriptors,泛化个性化属性(attribute)存取方式
  • 静态方法与类方法(static methods and class methods)
  • properties (即时计算的attributes)
  • decorators (Python2.4引入)
  • slots
  • 新的MRO(Method Resolution Order, 方法解析顺序)
接下来几个章节我将尝试介绍一下这些概念

低级构造方法和__new__()
往常,类的实例创建后,通过__init__()方法进行初始化。然而有时类的作者希望能控制实例的创建过程-例如,当对象是从存储数据恢复而来时。旧式类(Old-style classes)从未真正提供定制对象创建的机制,虽然有些库模块(例如,"new"模块)提供了某些情况的对象创建非标准方式。

新类引入了新的类方法__new__(),以方便类作者控制类实例创建。类作者通过重载__new__()可以实现一些设计模式,例如(从空闲列表)返回已创建实例的单模式(Singleton Pattern),或者返回其它类(可能是原来类的子类)的实例。__new__还有其它重要用途值得关注,例如,在pickle模块中,在反序列化对象时__new__用于创建实例。这时实例已经生成,而__init__方法尚未调用。

__new__还可用于辅助子类化不可变类型(immutable types)。因为"不可变"这一特性,这类对象无法通过标准的__init__()方法来初始化。于是任何特殊的初始化,需要在对象创建时进行;例如,如果类希望能修改不可变对象包含的值,可在__new__方法对此进行处理,向基类的__new__方法传递修改后的值来实现。

Descriptors

Descriptors 是绑定方法(bound methods)这一概念的泛化,绑定方法对于经典类(classic classes)实现至关重要。在经典类情况,当要查询的实例属性(instance attribute)在实例字典(instance dictionary)中未找到时,就继续在类字典中(class dictionary)查找,然后是递归遍历基类的类字典。当属性是在类字典(class dictionary)中查找到时(而不是在实例字典中)。解释器会检查查找到的对象是否是一个Python函数对象(function object)。如果是,则返回值不是该查找到的查找到的对象,而是将该对象的包装对象(wrapper object)作为一个currying function返回。当包装对象被调用时,它会把实例插入到参数列表最前面,然后调用原来的函数对象。

例如,类C有实例x。假设要做方法调用x.f(0)。这个操作会查找x的属性名"f",然后将0作为参数进行调用。如果"f"是类方法,在查找该属性时会返回一个包装函数(wrapper function),可用如下Python伪代码近似描述:


def bound_f(arg):
return f(x, arg)


当传递参数0来调用包装函数时,它会传递两个参数调用"f":x和0。这是类方法得到"self"参数的基本机制。

另外一种调用函数对象(function object)f(无包装)的方式是通过调用类C的属性名"f"。这时返回的不是包装后的对象而是函数f本身。换句话说,x.f(0)等价于C.f(x, 0)。这是Python中的一种相当基本的等价方式。

对经典类而言,如果查找到的属性是其它类型对象(非函数类型),则不创建包装对象,而是不做变化直接将该类属性返回。这使得类属性可以作为实例变量"缺省"值。例如在上例中,如果类C有一个名为"a"的属性,它的值是1,且x的实例字典中没有"a",那么x.a等于1。对x.a赋值则会在x的实例字典创建名为"a"的键,它的值将会遮蔽(shadow)同名类属性(因为属性查找顺序)。当x.a被删除后,被遮蔽的值(1)又能访问到了。

不幸的是,一些Python开发人员发现这种机制存在缺陷。缺陷之一是这种机制下无法创建"混合"类,也就是部分方法用Python实现,部分方法用C实现的类,因为这种机制下只有Python函数才会被包装,使得调用时能访问实例,而且这一行为是硬编码在语言中。而且也没有明显的方式来定义其它类型函数,例如C++和Java程序员熟悉的静态成员函数。

为了解决这一问题,Python 2.2对上述包装行为的进行了直观的泛化。不再用硬编码方式指定只对Python函数对象进行包装,而对其它对象不做包装。包装相关行为完全留给属性搜索到的对象来处理(上例中是函数f)。如果查找属性时的查找结果是有一个名为__get__特殊方法的对象,则该对象被认为是一个"descriptor"对象,这时__get__方法被调用,由它的返回值都作为属性查找结果。如果该对象没有__get__方法,则不作变化直接返回查找到的对象。要得到原有行为(对函数对象进行包装)而且不需要在实例属性查找代码中对函数对象特殊处理,现在拥有一个__get__方法的函数对象将如同以前一样返回包装对象。而且,用户还可以定义含有名为__get__方法的另外一个类,当这种类的实例在原来的类字典中被属性查找过程找到时,可以按需灵活进行包装。

在对属性查找这一概念进行泛化后,同样思路也有必要推广到属性的赋值与删除操作。因此,对赋值操作x.a=1或者删除操作 del x.a也有相似的处理机制。这时,如果属性"a"是在实例的类字典(而不是实例字典)中,检查类字典的中该对象是否含有名为__set__和__delete__的特殊方法(__del__已另有它用)。这样,通过实现这些函数,一个descriptor对象可以完全控制一个属性的get、set和delete含义。要强调的是,这些定制操作仅是对类字典中descriptor实例而言的,对象实例字典中则无效。

staticmethod, classmethod, and property

Python2.2利用这一新的descriptors机制增加了三个预定义类:类方法(classmethod),静态方法(staticmethod)和property,类方法和静态方法只是简单包装函数对象,通过__get__方法的具体实现,来返回不同种类的封装器(wrappers)。例如,静态方法在函数调用时封装器对参数列表不做任何修改。类方法在函数调用时将封装器将实例的类对象增加为第一个参数,而不是增加实例本身。这两种类型的方法都可以通过实例或者类来调用,且在不同调用者情况下调用时的参数相同。

property类封装器将取值和赋值这一对方法转换为一个"属性"。例如,如果你有如下一个类,


class C(object):
def set_x(self, value):
self.__x = value
def get_x(self):
return self.__x


property封装器可用来产生一个属性"x",当存取x时隐含方式调用了get_x和set_x方法。

一开始引入类方法、静态方法和property这些descriptors时,并没有同时引入相应的新语法。当时看起来同时引入比较重要的新特性以及相应的语法引起的争议过大(新语法的讨论总是引起激烈争论)。因此使用这些特性时,先依照常规定义类和方法,然后用额外语句来"包装"方法,例如:


class C:
def foo(cls, arg):
...
foo = classmethod(foo)
def bar(arg):
...
bar = staticmethod(bar)


对properties也有类似机制:


class C:
def set_x(self, value):
self.__x = value
def get_x(self):
return self.__x
x = property(get_x, set_x)


Decorators

这种方式有一个不利之处,用户阅读类的代码时,只有读完一个方法的声明,到了结尾部分才知道该方法是不是类方法或者静态方法(或其它用户自定义形式)。终于,Python2.4引入了新的语法,允许以如下方式撰写代码:


class C:
@classmethod
def foo(cls, arg):
...
@staticmethod
def bar(arg):
...


在函数声明前一行构造的@表达式(@expression)称为decorator。(不要和前面的descriptor混淆,实现__get__包装时所讨论到的才是descriptor。)选择这个decorator语法(decorator syntax)(从Java的annotations借鉴而来。)引起了无休止的争论,最终由BDFL宣告(BDFL pronouncement)来确定。(David Beazley写了一篇关于术语BDFL的历史典故,我会另外单独写一篇贴出。)

decorator特性已成为最成功的Python语言特性之一,各种decorator应用超过我当初最乐观的期待。特别是在Web框架中大量应用。鉴于如此成功,在Python2.6中,decorator语法从函数定义进一步扩展到类定义。

Slots

descriptor的引入带来了另外一个新特性,也即类的__slots__属性。例如一个类,定义如下:


class C:
__slots__ = ['x','y']


这儿的__slots__有如下含义。首先,对象的有效属性名被限制为__slots__值的列表。其次,由于属性名范围已经确定,__dict__属性因不再需要而被移除(除非某个基类已经包含了__dict__属性,子类化当前类且不包含__slots__属性可以再次拥有__dict__属性)。这样各属性存储在预先确定顺序的array中。因此,每一个slot属性实际是一个descriptor对象,利用该属性在array中的位置(index)来调用其set/get方法。在具体实现方面,该特性完全由C语言实现,性能优异。

一些人错误的认为__slots__的目的在于(通过限制属性名)增加代码的安全性。实际上,我的终极目标聚焦在性能方面。__slots__是一个有趣的descriptor应用,我担心对类的改动总是带来性能方面的不利影响。以descriptor而言,为了使之正常工作,对象属性的任何操作都需要首先检查类字典来判定该属性是否实际上是一个数据descriptor(data descriptor)。如果确实是,则由该descriptor负责属性存取,而不是通常情况下直接操作属性字典。这个额外的检查也意味着检查每个实例的字典之前要有一个额外的查找操作。因此,__slots__可以作为一种优化数据属性的方法,作为当有人对新类的性能不满意时的补救。结果证明这种性能的担心并不必要,但是撤除__slots__也为时已晚了。而且,正确使用slots确实能增加性能,尤其是创建大量小对象时有利于降低内存占用。

我把Python MRO(Method Resolution Order方法解析顺序)的介绍留在下一篇。

2010年6月22日星期二

New-style Classes

新类

英文原文链接: http://python-history.blogspot.com/2010/06/new-style-classes.html

原文作者: Guido van Rossum

[漫长的间歇,本系列blog回归了!我将接着去年的写,我尽量加快速度。]

前面我提到Python中对类(Class)的支持是后来添加的。具体实现符合典型的Python式“抄近路”哲学(cut corners),然而随着Python的发展,类(Class)的实现暴露出各种问题,也成为了Python高级用户的热门话题。

类(Class)实现问题之一是限制了对内置类型(built-in types)的子类化(subclass),例如lists, dictionaries, strings和其它一些对象(object)成为无法子类化的“特殊”类型。对于一门“面向对象(object oriented)”语言来说,这种限制多少有些尴尬。

另一问题是整个类型系统(type system)对于用户自定义类(user defined classes)来说是“错”的。假如你创建两个对象a和b,即使a和b是毫无关联的两个类的实例,语句type(a) == type(b)的值仍为真。无须讳言,对于熟悉C++或者Java语言的程序员来说,由于C++和Java等语言中类(Class)与底层的类型系统(type system)紧密相连,该现象相当诡异。

在Python的2.2版本,我重写了类实现,而且是“正确的实现”。这次重写一个Python主要子系统是目前为止最为野心勃勃的变动,肯定会有人因此说我得了“第二系统综合症(second-system syndrome)”。这次重写,不仅解决了内置类型的子类化这一直接问题,我还增加了对真正的元类(metaclass)的支持,并尝试改善原来多重继承(multiple inheritance)时过于简陋的方法解析顺序(method resolution order)。这项工作的主要受Ira Forman和Scott Danforth撰写的《Putting Metaclasses to Work》影响,书中阐明了元类的概念,及其与Smalltalk中类似概念的区别。

这次重写类实现的一个特点是,新类(new-style class)是作为语言的一个新特性引入,而不是完全替换掉旧的类。实际上为了保持向后兼容,Python 2中缺省生成的类仍然是旧的类。要创建一个新类,你只需子类化一个现存的新类,例如object(object是新类类层次的根)。例如:


class A(object):
statements
...


这次新类的引入取得的成绩斐然。新的元类得到了框架实现者的积极支持,实际上由于例外情况变少也使得讲解类更为容易。向后兼容使得新类进化过程中旧类依然正常工作。最后,虽然旧类终将从语言中移除,用户已习惯了用"class MyClass(object)"来声明一个类,也不错。
--
cut corners可参考前期博文:英文中文译文

second-system syndrome可参考:http://en.wikipedia.org/wiki/Second-system_effect

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

用类来表示异常

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