Metaclasses in Python

什么是 metaclass ?

In object-oriented programming, a metaclass is a class whose instances are
classes. Just as an ordinary class defines the behavior of certain objects, a
metaclass defines the behavior of certain classes and their instances.

简单的说, metaclass 就是 class 类型对象的 class, metaclass 的实例对象是 class 类型对象.

metaclass 能用来做什么 ?

我们就可以用 metaclass 来动态生成或者更改 class 的定义.

下面分别是通过传统 class 方式以及 metaclass 动态定义类 A

传统 class 方式定于类 A
1
2
class A(object):
    a = 'I am a string.'
metaclass 动态定义类 `A`
1
A = type('A', (object,), {'a': 'I am a string.'})

上面我们通过type类的实例化来生成类A, 如果我们想自定义类的生成, 我们可以以type类为基 类派生出自定义的 metaclass.

注: 本文所有代码在 python2.6 下能执行通过, 不能确保在 python3 下无误.

继承type类实现 metaclass

我们可以从type类派生出新的 metaclass, 然后通过重新实现__new__, __init__, __call__来实现类或者类实例的生成.

Meta类 meta.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Meta(type):
    def __call__(self):
        print 'Enter Meta.__call__: ', self
        obj = type.__call__(self)
        print 'Exit Meta.__call__: ', obj
        return obj

    def __new__(metacls, name, bases, dictionary):
        print 'Enter Meta.__new__:', metacls, name, bases, dictionary
        newClass = type.__new__(metacls, name, bases, dictionary)
        print 'Exit Meta.__new__: ', newClass
        return newClass

    def __init__(cls, name, bases, dictionary):
        print 'Enter Meta.__init__: ', cls, name, bases, dictionary
        super(Meta, cls).__init__(name, bases, dictionary)
        print 'Exit Meta.__init__'

print 'Create class A'
A = Meta('A', (object,), {})

print
print 'Create instance of class A'
A()

当我们运行上述的python文件, 会得到下面的输出结果:

1
2
3
4
5
6
7
8
9
10
11
$ python meta.py
Create class A
Enter Meta.__new__: <class '__main__.Meta'> A (<type 'object'>,) {}
Exit Meta.__new__:  <class '__main__.A'>
Enter Meta.__init__:  <class '__main__.A'> A (<type 'object'>,) {}
Exit Meta.__init__

Create instance of class A
Enter Meta.__call__:  <class '__main__.A'>
Exit Meta.__call__:  <__main__.A object at 0xb76a9ccc>
$

通过上面的输出结果, 我们可以看出:

  • Meta.__new__是用来生成类A类型对象, 我们可以在调用type.__new__之前更改 dictionary变量来增加/修改/删除新生成类A的成员变量/方法.
  • Meta.__new__是在生成类A类型对象后被调用类进行类A的初始化. 第一个参数cls 是已经生成的类A类型对象, 可以通过直接修改cls来修改类的定义, 例如增加成员变量.
  • Meta.__call__是在生成类A实例对象时被调用的, 通过调用type.__call__可以 生成该实例对象obj, 之后我们可以直接修改obj来实现实例对象的自定义.

如何使用 metaclass ?

  • 我们可以直接调用type或者Meta动态生成新的类型对象A:
实例化Meta来生成新的类
1
A = Meta('A', (object,), {})
  • 在定义类的时候, 重新指定该类的__metaclass__属性为Meta:
构建类的__metaclass__
1
2
3
class A(object):
    __metaclass__ = Meta
    # other member variables/methods definition

注意: 定义__metaclass__生成的类A会增加_A__metaclass成员变量, 在某些场景下需要 了解到这个区别.

函数方式定义类的 metaclass

类的__metaclass__可以是一个type类型的子类, 也可以是一个函数, 该函数接受三个参数:

  • name: 字符串类型, 表示新生成类型对象的名称
  • bases: tuple类型, 新生成类型对象的父类列表
  • properties: 字典类型, 新生成类型对象的属性

该函数必须返回新生成的类型对象. 下面例子是一个简单实现:

1
2
3
4
5
6
def meta_func(name, bases, properties):
    # you can modify bases, properties here to overide class creation
    return type(name, bases, properties)

class A(object):
    __metaclass__ = meta_func

使用 metaclass 的案例

  • 动态修改类的方法和属性, 例如给方法增加decorator
  • 类的序列化和反序列化
  • 在生成类的时候进行接口检查和接口注册
  • 对第三方库进行monkey patch
  • 生成代理类
  • 动态mixin
  • 控制实例对象的生成, 例如单体实例, 监控所有生成的实例对象

搜索stackoverflow 能找到大量使用 metaclass 的设计模式.

使用 metaclass 的原则

metaclass 会降低代码的可读性, 并且很多使用 metaclass 的场景都有替代方案, 因此必须牢记下面的 忠告:

Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).

Tim Peters Python Guru

参考

Comments