类也是对象
在理解metaclass
之前(metaclass
也被称为元类
,这里就不翻译了),你要先掌握python中的class。python中class的概念非常奇特(一切皆对象),这一点借鉴了Smalltalk
这门语言。
在大部分语言中,class只是用来描述如何产生一个object(翻译成对象
,这里也不翻译了)的代码段。这在python中也成立:
In most languages, classes are just pieces of code that describe how to produce an object. That’s kinda true in Python too:
|
|
但是Python中的class不止如此。class也是object。
对,类也是对象
只要你使用了class关键字,Python就会创建一个object。下面的代码段在内存中创建了一个名为ObjectCreator
的object:
|
|
这个object(这个class)本身就具有创建object的能力,这就是为什么它是一个类。
但是,它同时也是一个object,所以你可以:
- 将它赋值给一个变量
- 复制它
- 给它添加属性
- 将它作为函数参数进行传递
e.g.:
|
|
动态创建类
因为类即是对象,所以你可以动态地创建它们,就像创建一个普通的object。
首先,你可以在一个函数中用class关键字来创建一个类。
|
|
但是这还不够动态,因为你还是需要自己写整个类。
因为类即是对象,它们一定是由什么东西生成的。
当你使用class关键字,Python会自动创建这个对象。但是就像Python中其他东西一样,Python总会给你提供一种方法来手动操作它。
还记得type函数吗?这个古老而强大的函数可以告诉我们一个对象的类型是什么:
|
|
well,type还有一个截然不同的能力,它也能动态创建类。type可以接受一个类的描述作为参数,然后返回一个类。(我知道,同一个函数因为传入的参数不同而表现出完全不同的用法是很傻的。但是这是Python为了保持向后兼容)
type
这样工作:
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)
e.g.:
|
|
你可能注意到我们用MyShinyClass
作为了类的名字,也用它来作为变量接受类的引用。我们可以给他们取不同的名字,但是没有必要将事情复杂化。
type
接受一个字典来定义类的属性,所以:
|
|
并且,可以继承它:
|
|
这个也可以写作为:
|
|
最终你可能会希望为你的类增加方法。只需要定义一个有恰当签名的函数并且作为属性赋值就可以了。
|
|
动态创建了类之后,你能够给类添加更多的方法,就跟给一个正常创建的对象添加方法一样。
|
|
可以看到:在Python中,类是对象,你可以动态创建类。这些就是当你使用class
关键字的时候,Python在幕后做的事。而这些,都是通过metaclass来实现。
什么是metaclass
(终于开始了)
metaclass是创建class的“东西”。
你定义class是为了创建对象是吧?但是我们也知道Python中的类也是对象。
well,metaclass就是用来创建这些对象(类)的。它们是class的class,你可以理解为:
|
|
你已经看到了type
让你可以这样做:
|
|
这是因为type
方法本质上就是一个metaclass。type是Python在背后用来创建所有类的metaclass。
现在你可能想搞清楚为什么type
要全部用小写字母,而不是写成Type
?
我想这是为了要和str、int保持一致。type只是用来创建类对象的类。你可以通过查看__class__
属性来验证这一点。在Python中,一切,所有一切都是对象。包括整数,字符串,函数和类,它们都是对象。并且它们都是从一个类创建而来。
|
|
所以,一个metaclass仅仅是创建类对象的东西。如果你愿意,你可以称呼它为类工厂
。
type
是Python使用的内置的metaclass,当然,你也可以创建自己的metaclass。
metaclass属性
当你定义一个类的时候,你可以添加一个__metaclass__
属性。
|
|
如果你这样做了,Python将会使用这个metaclass去创建Foo类。
注意了,这里面有一些技巧。
你首先写下了class Foo(object)
,但是这个时候类对象Foo还没有被在内存中创建。
Python将会在类的定义中寻找__metaclass__
属性。如果找到了,Python就会用它来创建类对象Foo。如果没有,那它就用type
来创建这个类。
请把上面一段多读几遍。
当你写下:
|
|
Python做了下面的工作:
Foo中有__metaclass__
属性吗?
如果有,使用__metaclass__
的值在内存中创建一个类对象(我说的是类对象,请紧跟我的思路),取名为Foo。
如果Python找不到__metaclass__
,他会在MODULE层面继续找__metaclass__
(所谓MODULE层面,可以理解我类定义的文件内,把这个文件看成一个MODULE),并且做和前面相同的操作(但这只适用于没有继承的类,也就是旧式类)。
如果仍未找到__metaclass__
,它会用Bar(第一父类)的metaclass(可能是默认的type
)来创建类对象。
Be careful here that the __metaclass__
attribute will not be inherited, the metaclass of the parent (Bar.__class__
) will be. If Bar
used a __metaclass__
attribute that created Bar
with type()
(and not type.__new__()
), the subclasses will not inherit that behavior.
现在最大的问题是,你能给__metaclass__
赋什么值?
答案是:可以创建一个类的值。
那什么可以创建一个类呢?type
或者它的子类,或者任意用到了它的东西。
自定义 metaclass
metaclass的主要目的就是在类创建的时候,动态地修改它。
通常你会为API做这些工作,因为你想创建符合当前上下文的类。想象一个很傻的例子,你想让你的模块内所有的类的属性都大写。有好几种方法能达到这个需求,其中一种就是在MODULE层面设置__metaclass__
。
通过这种方法,所有这个模块内的类都会用这个metaclass来创建,我们只需要告诉这个metaclass来将所有的属性都转换成大写。
幸运的是,__metaclass__
可以是任意可调用的东西,它并不需要是一个普通的类(我知道,某些名字里带有‘class’的东西并不需要是一个class,请理解这一点,这很有帮助)。所以我们用一个函数作为一个简单的例子开始。
|
|
现在,我们用一个真正的类作为metaclass来做相同的事情:
|
|
但是,这种写法并不OOP
(Object-oriented programming
,面向对象编程),我们直接调用了type
,并且我们没有覆盖或者调用父类的__new__
方法,让我们这么做:
|
|
你可能已经注意到了有个额外的参数upperattr_metaclass
,这并没有什么特别的。__new__
方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。
当然了,为了清晰起见,这里的名字我起的比较长。但是就像self一样,所有的参数都有它们约定俗成的名称。因此,在真实的产品代码中一个元类应该是像这样的:
|
|
我们还可以使用super
让它更清晰一点,这会ease inheritance
(因为你可以拥有metaclass,从metaclass继承,从type继承)
|
|
就是这样,除此之外,对于metaclass真的没什么可说的了。
使用了metaclass的代码看起来复杂的原因并不是因为metaclass本身,而是因为你通常会用metaclass做一些晦涩的事情,依赖于自省、控制继承,和像__dict__
这样的变量等。
诚然,metaclass用来搞黑魔法
非常合适,因而会搞出来复杂的东西。但是就metaclass本身而言,他们非常简单:
- 拦截类的创建
- 修改这个类
- 返回被修改过的类
为什么要用metaclass而不是函数?
既然metaclass可以接受任意可调用的对象,那为什么我们还要用一个类来给它赋值,这样不是明显更复杂吗?
这样做有下面几个理由:
- 意图会更加明显。当你读到
UpperAttrMetaclass(type)
的时候,你知道接下来会发生什么。 - 你可以使用OOP,metaclass可以继承自别的metaclass,覆写父类的方法。metaclass甚至可以使用metaclass。
- 你可以将代码组织得更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
- 你可以使用
__new__
,__init__
以及__call__
这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__
里处理掉,有些人还是觉得用__init__
更舒服些。 - 这些类被称为metaclass,靠,这肯定意味这什么,我要小心!
究竟为什么要使用metaclass
下面回答我们最大的问题,为什么我们要使用这个晦涩难懂且容易出错的特性?
好吧,一般来说,你根本用不上它:
“元就是深 度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters
metaclass最多的应用场景是创建API。一个典型的例子是Django的ORM系统。它允许你像下面一样定义一些东西:
|
|
但是如果你这样做的话:
|
|
它不会返回一个IntegerField
对象,而是返回了一个整数,甚至可以从数据库里取出数据。
这个可能是因为models.Model
定义了__metaclass__
,它用了一些魔法来将你刚刚定义的简单的Person
类转换成对数据库的一个复杂的hook。Django通过暴露一个使用metaclass的API让这些复杂的东西简化,通过这个API重新创建代码,在背后完成真正的工作。
##结语
首先,你要知道类其实是能够创建出类实例的对象。事实上,类本身也是实例,他们是metaclass的实例。
|
|
在Python中,一切皆对象,它们不是类的实例就是metaclass的实例。
除了type
type实际上是它自己的metaclass。在纯Python环境中这是不可能做到的(你不能创建一个类,metaclass是这个类自身),这是通过在实现层面通过一些手段做到的。
其次,metaclass很复杂。对于一些非常简单的类的更改,你并不需要使用metaclass。你可以用下面两个不同的技术来修改类
- monkey patching
- class decorators
当你需要动态修改类的时候,99%的情况下,你最好用上面两个方法。当然了,其实在99%的情况下,你根本不需要修改类:D
##The Metaclass Hook in Python 3
Python3改变了metaclass的语法。你仍然可以定义__metaclass
属性,但是Python会忽略它。取而代之的是,你可以在基类列表中使用一个关键字参数:
|
|
这意味着在Python3中,直接将__metaclass__
定义成一个类或者一个方法都不再可行。所有的metaclass都必须被定义成单独的类。这种方法也有好处,它让metaclass更具有一致性,当然也更易读和理解。