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