也许写代码的时候没有写过元类,但是它的概念和原理还是要理解。

目录

type()

类是元类的实例。因为在Python中万物皆对象,其实类和元类也都是对象。在Python中默认的元类是type

type作为一个函数时,它既可以查看对象所属的类,还可以创建一个类。除此之外type也是一个类,是它自己的类。

使用type查看对象所属的类。同时可以看到type的类就是type

In [29]: class Foobar(object):
    ...:     pass
    ...:

In [30]: foo = Foobar()

In [31]: type(foo)
Out[31]: __main__.Foobar

In [32]: type(Foobar)
Out[32]: type

In [33]: type(type)
Out[33]: type

正是因为前面提到的类也是对象,所以也可以动态的创建类。使用type动态创建类,需要的参数依次为:类名,父类的元组,属性字典。

In [41]: def hello(self, name=None):
    ...:     return 'hello, {}'.format(name or self.name)

In [43]: SayHello = type('SayHello', (object,), {'name': 'Snail', 'hello': hello})

In [44]: SayHello
Out[44]: __main__.SayHello

In [45]: action = SayHello()

In [46]: action.hello()
Out[46]: 'hello, Snail'

In [47]: action.hello('world')
Out[47]: 'hello, world'

metaclass

使用type()可以动态创建类。这也是metaclass的作用,控制类的创建行为。

如何创建自己的元类

在Python中,通过创建继承自type的类来定义新的元类。无论是Python2还是Python3,我们都可以通过下面的方式创建自己的元类

class MyMeta(type):
    pass

使用元类创建类时,Python2是使用添加__metaclass__属性的方式。

class MyClass(object):
    __metaclass__ = MyMeta
    pass

而在Python3中是在类定义中添加metaclass参数。

class MyClass(object, metaclass=MyMeta):
    pass

__metaclass__属性

我们已经知道Python2使用__metaclass__属性来创建类。所以在真正创建类之前Python会先查找__metaclass__属性。查找的顺序:

  1. 在类定义中查找
  2. 在父类中查找[2] [3]
  3. 在MODULE层查找__metaclass__属性。只有在Python2中,并且创建的类是old-style类时[1]。

写一个元类

下面我们试着真正的写一个元类。假设一个问题:“我们希望模块中所有类的属性都是大写”。

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)

试着去理解,如果有看不明白的,可以在了解Python对象实例化后再回来理解这个例子。

什么时候需要用元类

一种流行的说法

Metaclasses are deeper magic that 99% of users should never 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). – Python Guru Tim Peters

其实在日常使用中,你有可能用过metaclass。

  • Django中ORM。Django在models.Model模块中定义了__metaclass__
  • acb模块中的ABCMeta

reference


注[1]:old-style类是指class MyClass: pass这种类定义方式。对应的new-style是指class MyClass(object): pass

class OutMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        print 'OutMetaclass', clsname
        return super(OutMetaclass, cls).__new__(cls, clsname, bases, attrs)

__metaclass__ = OutMetaclass

class NewClass(object):
    pass

class OldClass:
    pass

得到的结果

➜  test python A.py
OutMetaclass OldClass

注[2]:我在Python2.7中试验过一种写法(为了举例子)

class OutMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        print 'OutMetaclass', clsname
        return super(OutMetaclass, cls).__new__(cls, clsname, bases, attrs)

__metaclass__ = OutMetaclass

class InMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        print 'InMetaclass', clsname
        return super(InMetaclass, cls).__new__(cls, clsname, bases, attrs)

class MyClass(object):
    __metaclass__ = OutMetaclass

class My2Class(MyClass):
    __metaclass__ = InMetaclass

会得到如下错误(这是需要注意的点):

➜  test python A.py
OutMetaclass MyClass
InMetaclass My2Class
Traceback (most recent call last):
  File "A.py", line 19, in <module>
    class My2Class(MyClass):
  File "A.py", line 13, in __new__
    return super(InMetaclass, cls).__new__(cls, clsname, bases, attrs)
TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

注[3]:是先在module这种全局中查找,还是先在父类中查找?Stack Overflow中说先在module层查找。但是我的实验结果是先在父类中查找。

class OutMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        print 'OutMetaclass', clsname
        return super(OutMetaclass, cls).__new__(cls, clsname, bases, attrs)

__metaclass__ = OutMetaclass

class InMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        print 'InMetaclass', clsname
        return super(InMetaclass, cls).__new__(cls, clsname, bases, attrs)

class OldClass:
    __metaclass__ = InMetaclass

class SubOldClass(OldClass):
    pass

结果如下(说明先在父类中查找):

➜  test python A.py
InMetaclass OldClass
InMetaclass SubOldClass