Python 元类

概述

   Python 是内置面向对象编程(OOP)的语言。在 Python 中,everything is a object,类也不例外。当把类看作对象时,类也是可以是被动态创建的。元类(Metaclass)就可以动态创建类对象,可以称它为“类工厂”。type 是 python 内置的元类,用户也可以创建自己的元类。

本文主要翻译自 Python Metaclass by John Sturtz 。

元类是非常难掌握的 OOP 概念,它隐藏在所有 Python 代码下面,甚至在你没有意识到它的存在的地方。在大多数情况下,你都不需要或者极少需要考虑它的存在。

自定义元类的做法是有些争议的,就像 Tim Peter 建议的那样:

元类是比99%的用户本应该担心的更深的魔法。当你想弄清楚是否需要使用它们时,那你就不需要它们(那些实际用到它们的人都清楚地知道需要它们,而不需要解释原因)。
—— Tim Peters

然后,理解 Python 元类还是值得的,因为它通常可以更好地帮助理解 Python 类的内部实现。
你绝不知道:可能在某一天发现使用自定义元类就是你需要的。

新类 VS 旧类

在新类中,统一了 classtype的概念。如果一个 obj 时一个新类的实例(instance),那么type(obj)obj.__class__是相同的。

1
2
3
4
5
6
7
8
9
>>> class Foo:
... pass
>>> obj = Foo()
>>> obj.__class__
<class '__main__.Foo'>
>>> type(obj)
<class '__main__.Foo'>
>>> obj.__class__ is type(obj)
True

Type 和 Class

在 Python3 中,所有的类都是新类,因此,互换地引用对象的类型(object’s type)和对象的类是合理的。

类也是对象,那么类也有它自己的类型(type),那么类的类型是什么呢?

1
2
3
4
5
6
7
8
9
10
11
>>> class Foo:
... pass
...
>>> x = Foo()

>>> type(x)
<class '__main__.Foo'>

>>> type(Foo)
<class 'type'>

Foo 的类型是 type,通常情况下,所有新类的类型都是 type,包括用户十分熟悉的内置类:

1
2
3
4
5
6
7
8
9
>>> for t in int, float, dict, list, tuple:
... print(type(t))
...
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>

type 的类型是它自己:

1
2
>>> type(type)
<class 'type'>

type是元类,其他类是它的实例。

动态定义类

内置的 type() 当被传递一个参数时,它返回参数对象的类型,对于新类,它通常和对象的 __class__属性是相同的。
但也可以向 type()传递三个参数 type(<name>, <bases>, <dct>):

  • name 代表类名,另外该名字会成为类的 __name__ 属性。
  • bases 代表父类的元组,它会成为类的 __bases__ 属性。
  • dct 代表类的命名空间字典,其中包含了类体,它会作为类的 __dict__ 属性。

这样调用type()函数会返回type元类的实例,或者说它动态创建了一个新的类。

在下面的每个示例中都包含两个代码片段,上面的一个使用 type()动态创建类,下面的一个使用class语句定义类,这两个片段在功能上是等价的。

示例1

在这个例子中,type()函数的 basesdct 参数都为空:

1
2
3
4
5
>>> Foo = type('Foo', (), {})

>>> x = Foo()
>>> x
<__main__.Foo object at 0x04CFAD50>
1
2
3
4
5
6
>>> class Foo:
... pass
...
>>> x = Foo()
>>> x
<__main__.Foo object at 0x0370AD50>

示例2

在这里,bases 参数是一个单成员元组,该成员 Foo 作为所创建类的父类。另外,在命名空间中设置了属性 attr并初始化 100:

1
2
3
4
5
6
7
8
9
10
>>> Bar = type('Bar', (Foo,), dict(attr=100))

>>> x = Bar()
>>> x.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)

1
2
3
4
5
6
7
8
9
10
11
12
>>> class Bar(Foo):
... attr = 100
...

>>> x = Bar()
>>> x.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)

示例3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> Foo = type(
... 'Foo',
... (),
... {
... 'attr': 100,
... 'attr_val': lambda x : x.attr
... }
... )

>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100
1
2
3
4
5
6
7
8
9
10
11
12
>>> class Foo:
... attr = 100
... def attr_val(self):
... return self.attr
...

>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100

示例4

自定义元类

考虑下面的例子:

1
2
3
4
>>> class Foo:
... pass
...
>>> f = Foo()

当解释器解析 Foo() 时,将会发生下面的动作:

  • Foo的父类的 __call__ 方法会被调用。在这里 Foo 的父类是 type,因此type__call__方法被调用。
  • __call__()方法依次调用下面的方法:
    • new()
    • init()

如果Foo没有定义 __new__()__init__(),其祖先的默认方法会被调用。Foo 可以通过定义这些函数来自定义自己实例化的行为。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def new(cls):
... x = object.__new__(cls)
... x.attr = 100
... return x
...
>>> Foo.__new__ = new

>>> f = Foo()
>>> f.attr
100

>>> g = Foo()
>>> g.attr
100

在上面的例子中,每次实例化 Foo类时,都会默认初始化一个 attr 属性为100。(通常,这种代码都出现在 __init__方法中而不是__new__中。这个例子仅仅作为一个 demo。)

如果你想要同样自定义类(例如 Foo)的创建行为,可以自定义 Foo 的类型的 __new__方法。但直接重新对 type__new__() 方法进行赋值是不允许的。

一种方案是自定义元类,该元类继承自 type:

1
2
3
4
5
6
7
>>> class Meta(type):
... def __new__(cls, name, bases, dct):
... x = super().__new__(cls, name, bases, dct)
... x.attr = 100
... return x
...

现在 Meta 也是元类,接下来完成另外一半:定义一个新类 Foo 并指定它的元类上面自定义的 Meta,而不是标准的 type。如下所示:

1
2
3
4
5
6
>>> class Foo(metaclass=Meta):
... pass
...
>>> Foo.attr
100

当然,任何采取上面同样定义的类都默认包含属性 attr。一个类函数可以作为创建对象的模板,一个元类函数则可以作为创建类的模板。元类也可以被称为 类工厂

是否真的必要?

实际上,第4节中的实现可以采用其他方案,例如类继承和类修饰符。

总结

通常情况下,创建自定义元类都是不必要的。如果对于当前问题存在更简单的解决方式,那么很可能就应该使用这种方式。但理解元类仍然是有利于你去理解python的类机制,并可以判断什么时候元类可以作为一个真正合适的工具来使用。