Python Metaclass

6 minute read

Metaclass 国内一般译为成「元类」,属于面向对象里的进阶用法。

Meta 一词根据 wikipedia 上的定义,一般用做前缀,例如,metadata (are data about data), metaprogramming (writing programs that manipulate programs), 所以,也可以同样定义 metaclass (are class that produce/manipulate classes)

在 Python 里,metaclass 也是一种类,它与 class 的关系就像 classobject 的关系,metaclass 可以改变它所生成的类的属性和行为。

下面以 Python3 为例介绍 metaclass 的语法和使用场景。

语法定义

Python3 里定义一个最简单的 class Foo, 并实例化一个对象:

class Foo:
    pass

>>> foo = Foo()
>>> foo
<__main__.Foo object at 0x1074a13a0>

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

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

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

另外,为了提供灵活度,也可以动态定义:

Foo2 = type('Foo2', (), {})

>>> foo2 = Foo2()
>>> foo2
<__main__.Foo2 object at 0x10e838be0>

>>> type(foo2)
<class '__main__.Foo2'>

>>> type(Foo2)
<class 'type'>

由此可见,对象 foo 是由 Foo 这个生成,而 Foo 是由 type 这个类生成。


接下来,我们可以改变下对象的属性,

class Foo:
    a = 42

>>> Foo.a
42
>>> Foo().a
42

已经定义好的 class 可以通过修改 __new__ 方法来实现,

def new(cls):
    x = object.__new__(cls)
    x.a = 42
    return x

Foo2.__new__ = new

>>> Foo2().a
42

我们也可以尝试修改 type__new__

>>> type.__new__ = new
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'type'

然而并不允许,因为一旦修改了 type, 那么就会影响到所有内置的 class。因此,Python3 提供了一种额外的机制,也就是 metaclass

class MyMeta(type):
    def __new__(cls, name, bases, dct):
        x = super().__new__(cls, name, bases, dct)
        if name == 'Bar2':
            x.a = 43
        else:
            x.a = 42
        return x

class Bar(metaclass=MyMeta):
    pass

class Bar2(metaclass=MyMeta):
    pass

>>> Bar.a
42
>>> Bar().a
42
>>> Bar2.a
43
>>> Bar2().a
43

metaclass, class, object 之间的关系可以如下图:

metaclass

应用场景

Singleton

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class MyClass(metaclass=Singleton):
    pass

Class Registry

models = {}

class ModelMeta(type):
    def __new__(meta, name, bases, dic):
        cls = super().__new__(meta, name, bases, dic)
        models[name] = cls
        return cls

class Model(metaclass=ModelMeta): pass

class ModelA(Model): pass

class ModelB(Model): pass

>>> models
{'Model': <class '__main__.Model'>, 'ModelA': <class '__main__.ModelA'>, 'ModelB': <class '__main__.ModelB'>}

ORM

class Field: pass

class String(Field): pass

class Integer(Field): pass

class ModelMeta(type):
    def __new__(meta, name, bases, attrs):
        fields = {}
        for key, value in attrs.items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (name, key)
                fields[key] = value
        for base in bases:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        attrs['_fields'] = fields
        return type.__new__(meta, name, bases, attrs)

class Model(metaclass=ModelMeta): pass

class ModelA(Model):
    id = Integer()
    name = String()

>>> ModelA._fields
{'id': <__main__.Integer object at 0x10e9fefd0>, 'name': <__main__.String object at 0x10e9fee20>}

阅读材料