Python Metaclass

这是e-satis同学在stackoverflow上的回答

类也是对象

在理解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:

1
2
3
4
5
6
7
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是Python中的class不止如此。class也是object。

对,类也是对象

只要你使用了class关键字,Python就会创建一个object。下面的代码段在内存中创建了一个名为ObjectCreator的object:

1
2
3
>>> class ObjectCreator(object):
... pass
...

这个object(这个class)本身就具有创建object的能力,这就是为什么它是一个类。

但是,它同时也是一个object,所以你可以:

  • 将它赋值给一个变量
  • 复制它
  • 给它添加属性
  • 将它作为函数参数进行传递

e.g.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态创建类

因为类即是对象,所以你可以动态地创建它们,就像创建一个普通的object。

首先,你可以在一个函数中用class关键字来创建一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

但是这还不够动态,因为你还是需要自己写整个类。

因为类即是对象,它们一定是由什么东西生成的。

当你使用class关键字,Python会自动创建这个对象。但是就像Python中其他东西一样,Python总会给你提供一种方法来手动操作它。

还记得type函数吗?这个古老而强大的函数可以告诉我们一个对象的类型是什么:

1
2
3
4
5
6
7
8
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

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.:

1
2
3
4
5
6
7
8
9
>>> class MyShinyClass(object):
... pass
can be created manually this way:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

你可能注意到我们用MyShinyClass作为了类的名字,也用它来作为变量接受类的引用。我们可以给他们取不同的名字,但是没有必要将事情复杂化。

type接受一个字典来定义类的属性,所以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> class Foo(object):
... bar = True
Can be translated to:
>>> Foo = type('Foo', (), {'bar':True})
And used as a normal class:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

并且,可以继承它:

1
2
>>> class FooChild(Foo):
pass

这个也可以写作为:

1
2
3
4
5
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

最终你可能会希望为你的类增加方法。只需要定义一个有恰当签名的函数并且作为属性赋值就可以了。

1
2
3
4
5
6
7
8
9
10
11
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

动态创建了类之后,你能够给类添加更多的方法,就跟给一个正常创建的对象添加方法一样。

1
2
3
4
5
6
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

可以看到:在Python中,类是对象,你可以动态创建类。这些就是当你使用class关键字的时候,Python在幕后做的事。而这些,都是通过metaclass来实现。

什么是metaclass(终于开始了)

metaclass是创建class的“东西”。

你定义class是为了创建对象是吧?但是我们也知道Python中的类也是对象。

well,metaclass就是用来创建这些对象(类)的。它们是class的class,你可以理解为:

1
2
MyClass = MetaClass()
MyObject = MyClass()

你已经看到了type让你可以这样做:

1
MyClass = type('MyClass', (), {})

这是因为type方法本质上就是一个metaclass。type是Python在背后用来创建所有类的metaclass。

现在你可能想搞清楚为什么type要全部用小写字母,而不是写成Type?

我想这是为了要和str、int保持一致。type只是用来创建类对象的类。你可以通过查看__class__属性来验证这一点。在Python中,一切,所有一切都是对象。包括整数,字符串,函数和类,它们都是对象。并且它们都是从一个类创建而来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
Now, what is the __class__ of any __class__ ?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

所以,一个metaclass仅仅是创建类对象的东西。如果你愿意,你可以称呼它为类工厂

type是Python使用的内置的metaclass,当然,你也可以创建自己的metaclass。

metaclass属性

当你定义一个类的时候,你可以添加一个__metaclass__属性。

1
2
3
class Foo(object):
__metaclass__ = something...
[...]

如果你这样做了,Python将会使用这个metaclass去创建Foo类。

注意了,这里面有一些技巧。

你首先写下了class Foo(object),但是这个时候类对象Foo还没有被在内存中创建。

Python将会在类的定义中寻找__metaclass__属性。如果找到了,Python就会用它来创建类对象Foo。如果没有,那它就用type来创建这个类。

请把上面一段多读几遍。

当你写下:

1
2
class Foo(Bar):
pass

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,请理解这一点,这很有帮助)。所以我们用一个函数作为一个简单的例子开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True
f = Foo()
print(f.BAR)
# Out: 'bip'

现在,我们用一个真正的类作为metaclass来做相同的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)

但是,这种写法并不OOPObject-oriented programming,面向对象编程),我们直接调用了type,并且我们没有覆盖或者调用父类的__new__方法,让我们这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)

你可能已经注意到了有个额外的参数upperattr_metaclass,这并没有什么特别的。__new__方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。

当然了,为了清晰起见,这里的名字我起的比较长。但是就像self一样,所有的参数都有它们约定俗成的名称。因此,在真实的产品代码中一个元类应该是像这样的:

1
2
3
4
5
6
7
8
9
10
11
12
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)

我们还可以使用super让它更清晰一点,这会ease inheritance(因为你可以拥有metaclass,从metaclass继承,从type继承)

1
2
3
4
5
6
7
8
9
10
11
12
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

就是这样,除此之外,对于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系统。它允许你像下面一样定义一些东西:

1
2
3
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()

但是如果你这样做的话:

1
2
guy = Person(name='bob', age='35')
print(guy.age)

它不会返回一个IntegerField对象,而是返回了一个整数,甚至可以从数据库里取出数据。

这个可能是因为models.Model定义了__metaclass__,它用了一些魔法来将你刚刚定义的简单的Person类转换成对数据库的一个复杂的hook。Django通过暴露一个使用metaclass的API让这些复杂的东西简化,通过这个API重新创建代码,在背后完成真正的工作。

##结语
首先,你要知道类其实是能够创建出类实例的对象。事实上,类本身也是实例,他们是metaclass的实例。

1
2
3
>>> class Foo(object): pass
>>> id(Foo)
142630324

在Python中,一切皆对象,它们不是类的实例就是metaclass的实例。

除了type

type实际上是它自己的metaclass。在纯Python环境中这是不可能做到的(你不能创建一个类,metaclass是这个类自身),这是通过在实现层面通过一些手段做到的。

其次,metaclass很复杂。对于一些非常简单的类的更改,你并不需要使用metaclass。你可以用下面两个不同的技术来修改类

  • monkey patching
  • class decorators

当你需要动态修改类的时候,99%的情况下,你最好用上面两个方法。当然了,其实在99%的情况下,你根本不需要修改类:D

##The Metaclass Hook in Python 3

metaclass在python3中语法有了变化

Python3改变了metaclass的语法。你仍然可以定义__metaclass属性,但是Python会忽略它。取而代之的是,你可以在基类列表中使用一个关键字参数:

1
2
class Simple1(object, metaclass = SimpleMeta1):
...

这意味着在Python3中,直接将__metaclass__定义成一个类或者一个方法都不再可行。所有的metaclass都必须被定义成单独的类。这种方法也有好处,它让metaclass更具有一致性,当然也更易读和理解。