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中经典类的消失)。

没有评论: