Fu
Simple is Beautiful!

python之metaclass

什么是metaclass

在 python 中,一切皆是对象,包括类。 对象均是由与其对应的类创建的,既然类也是对象,那么类是由与其对应的类创建的, 此处用来创建类的类就被称为元类,也就是本文所要讲的 metaclass。

>>> class Foo(object):
...     pass
...
>>> f = Foo()
>>> type(f)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'>

上面,首先我们定义了一个名为 Foo 的类,用其生成了一个对象实例 f, 我们可以用 type(obj) 来查看某个对象的类,可以看出,f 的类是 FooFoo 的类是 type, 而 type 就是我们所说的 metaclass。

有人也许会问 type 的类又是什么呢?

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

type 的类是 type 自己,也可以说 type 是 python 对象系统的源头,其底层实现请看 python 源码中的 PyType_Type 的定义。

class 的创建

在介绍 metaclass 之前,我们先来看看在 python 中类的创建过程,如下:

class Bar(object):
    def __init__(self, name):
        self.name = name

    def print_name():
        print(self.name)

上面示例通过 class 语句定义了一个 Bar 类,不仅如此,我们还可以用 type metaclass 来创建它:

cls_name = 'Foo'
cls_bases = (object,)
cls_body = '''
def __init__(self, name):
    self.name = name

def print_name(self):
    print(self.name)
'''
cls_attrs = {}
exec(cls_body, globals(), cls_attrs)

Foo = type(cls_name, cls_bases, cls_attrs)

上面示例展示了用 type metaclass 动态生成类的用法。

在用 class 语句自定义类时,默认 metaclass 是 type,我们也可以指定 metaclass 来创建类。 由于 python3 和 python2 在指定类的 metaclass 语法不兼容,下面分别示例 python2 和 python3 两个版本。

python2 版本:

class Bar(object):
    __metaclass__ = MetaClass

    def __init__(self, name):
        self.name = name

    def print_name():
        print(self.name)

python3 版本:

class Bar(object, metaclass=MetaClass):
    def __init__(self, name):
        self.name = name

    def print_name():
        print(self.name)

通过上面指定 metaclass 相关属性,python 解释器就会自动用 MetaClass 来创建 Bar 类了。

metaclass 自定义

metaclass 的主要目的就是为了当创建类时能够自动地改变类, 我们可以自定义一些 metaclass,进而改变创建类的行为。

自定义 metaclass 一般是通过 __new____init__ 方法来改变类的创建的:

class Metaclass(type):
    def __new__(cls, name, bases, attrs):
        # some code

    def __init__(self, name, bases, attrs):
        # some code

当然,有时是通过 __call__ 方法来自定义类:

class Metaclass(type):
    def __call__(cls, *args, **kargs):
        # some code

DocMeta

写代码时,我们通常规定:自定义类中所有方法必须有文档。 如果由程序员自己检查,不免会出现遗漏的地方,因此我们自定义一个 DocMeta 来自动检测。

如下:

class DocMeta(type):
    def __init__(self, name, bases, attrs):
        for key, value in attrs.items():
            if key.startswith('__'):
                continue
            if not hasattr(value, '__call__'):
                continue
            if not getattr(value, '__doc__'):
                raise TypeError('%s must have a docstring' % key)
        super(DocMeta, self).__init__(self, name, bases, attrs)

这样就可以用 DocMeta 来检测自定义类中方法是否有文档,如果没有,就会抛出 TypeError 异常:

>>> class Car(object, metaclass=DocMeta):
...     def __init__(self, value):
...         self.value = value
...
...     def change(self):
... 	    self.value += 1
...
TypeError: change must have a docstring

SingletonMeta

有时我们会有这样的需求:由同一个类创建的对象均指向同一个对象。 其实我们也可用 mataclass 实现这样的功能:

class SingletonMeta(type):
    instance = None
    def __call__(cls, *args, **kwargs):
        if not cls.instance:
             cls.instance = super(SingletonMeta, cls).__call__(*args, **kwargs)
        return cls.instance

class Singleton(metaclass=SingletonMeta):
    pass

>>> a = Singleton()
>>> b = Singleton()
>>> a is b
True

上面代码中,当我们调用 Singleton() 来创建对象 ab 时,解释器就会调用 SingletonMeta.__call__ 方法来保证创建对象的唯一性。

metaclass应用

python 界的领袖 Tim Peters 说过:

元类就是深度的魔法,99% 的用户应该根本不必为此操心。 如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。 那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。

元类的主要用途是创建 API。一个典型的例子是 Django ORM。它允许你像这样定义:

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

但是如果你像这样做的话:

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

这并不会返回一个 IntegerField 对象,而是会返回一个 int,甚至可以直接从数据库中取出数据。 这是有可能的,因为 models.Model 定义了 metaclass,并且使用了一些魔法能够将你刚刚定义的简单的 Person 类转变成对数据库的一个复杂 hook。 Django 框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的 API 将其化简,通过这个 API 重新创建代码,在背后完成真正的工作。

python15
2015-08-05 15:08:00