陌生的 metaclass

在 Python 中,一切皆对象。字符串,列表,字典,函数是对象,类也是一个对象,因此你可以:

  • 把类赋值给一个变量
  • 把类作为函数参数进行传递
  • 把类作为函数的返回值
  • 在运行时动态地创建类

看一个简单的例子:

在日常使用中,我们经常使用 来派生一个类,事实上,在这种情况下,Python 解释器会调用 type 来创建类。

这里,出现了 type,没错,是你知道的 type,我们经常使用它来判断一个对象的类型,比如:

  1. class Foo(object):
  2. Foo = True
  3. >>> type(10)
  4. <type 'int'>
  5. >>> type('hello')
  6. <type 'str'>
  7. >>> type(Foo())
  8. <class '__main__.Foo'>
  9. >>> type(Foo)
  10. <type 'type'>

事实上,type 除了可以返回对象的类型,它还可以被用来动态地创建类(对象)。下面,我们看几个例子,来消化一下这句话。

使用 type 来创建类(对象)的方式如下:

假设有下面的类:

  1. class Foo(object):
  2. pass

现在,我们不使用 class 关键字来定义,而使用 type,如下:

  1. Foo = type('Foo', (object, ), {}) # 使用 type 创建了一个类对象

上面两种方式是等价的。我们看到,type 接收三个参数:

  • 第 1 个参数是字符串 ‘Foo’,表示类名
  • 第 2 个参数是元组 (object, ),表示所有的父类
  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

在上面,我们使用 type() 创建了一个名为 Foo 的类,然后把它赋给了变量 Foo,我们当然可以把它赋给其他变量,但是,此刻没必要给自己找麻烦。

  1. >>> print Foo
  2. <class '__main__.Foo'>
  3. >>> print Foo()
  4. <__main__.Foo object at 0x10c34f250>

假设有下面的类:

  1. class Foo(object):
  2. foo = True
  3. def greet(self):
  4. print 'hello world'
  5. print self.foo

type 来创建这个类,如下:

上面两种方式的效果是一样的,看下使用:

  1. >>> f = Foo()
  2. >>> f.foo
  3. True
  4. <bound method Foo.greet of <__main__.Foo object at 0x10c34f890>>
  5. >>> f.greet()
  6. True

再来看看继承的情况,假设有如下的父类:

  1. class Base(object):
  2. pass

我们用 Base 派生一个 Foo 类,如下:

  1. class Foo(Base):
  2. foo = True

改用 type 来创建,如下:

  1. Foo = type('Foo', (Base, ), {'foo': True})

元类(metaclass)是用来创建类(对象)的可调用对象。这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:

  1. 类是实例对象的模板,元类是类的模板
  2. +----------+ +----------+ +----------+
  3. | | | | | |
  4. | | instance of | | instance of | |
  5. | instance +------------>+ class +------------>+ metaclass|
  6. | | | | | |
  7. | | | | | |
  8. +----------+ +----------+ +----------+

我们在前面使用了 type 来创建类(对象),事实上,type 就是一个元类

那么,元类到底有什么用呢?要你何用…

元类的主要目的是为了控制类的创建行为。我们还是先来看看一些例子,以消化这句话。

先从一个简单的例子开始,假设有下面的类:

1.首先,定义一个元类,按照默认习惯,类名以 Metaclass 结尾,代码如下:

  1. class PrefixMetaclass(type):
  2. def __new__(cls, name, bases, attrs):
  3. # 给所有属性和方法前面加上前缀 my_
  4. _attrs = (('my_' + name, value) for name, value in attrs.items())
  5. _attrs = dict((name, value) for name, value in _attrs) # 转化为字典
  6. _attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
  7. return type.__new__(cls, name, bases, _attrs) # 返回创建后的类

上面的代码有几个需要注意的点:

  • PrefixMetaClass 从 type 继承,这是因为 PrefixMetaclass 是用来创建类的
  • __new__ 是在 __init__ 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下:
    • cls:当前准备创建的类
    • name:类的名字
    • bases:类的父类集合
    • attrs:类的属性和方法,是一个字典

2.接着,我们需要指示 Foo 使用 PrefixMetaclass 来定制类。

在 Python2 中,我们只需在 Foo 中加一个 __metaclass__ 的属性,如下:

  1. class Foo(object):
  2. __metaclass__ = PrefixMetaclass
  3. name = 'foo'
  4. def bar(self):
  5. print 'bar'

在 Python3 中,这样做:

  1. class Foo(metaclass=PrefixMetaclass):
  2. name = 'foo'
  3. print 'bar'

现在,让我们看看使用:

  1. >>> f.name # name 属性已经被改变
  2. ---------------------------------------------------------------------------
  3. AttributeError Traceback (most recent call last)
  4. <ipython-input-774-4511c8475833> in <module>()
  5. ----> 1 f.name
  6. AttributeError: 'Foo' object has no attribute 'name'
  7. >>>
  8. >>> f.my_name
  9. 'foo'
  10. >>> f.my_bar()
  11. bar
  12. >>> f.echo('hello')
  13. 'hello'

可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar,这就是元类的魔法。

再来看一个继承的例子,下面是完整的代码:

  1. class PrefixMetaclass(type):
  2. def __new__(cls, name, bases, attrs):
  3. # 给所有属性和方法前面加上前缀 my_
  4. _attrs = (('my_' + name, value) for name, value in attrs.items())
  5. _attrs = dict((name, value) for name, value in _attrs) # 转化为字典
  6. _attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
  7. return type.__new__(cls, name, bases, _attrs)
  8. class Foo(object):
  9. __metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别
  10. name = 'foo'
  11. def bar(self):
  12. print 'bar'
  13. class Bar(Foo):
  14. prop = 'bar'

其中,PrefixMetaclass 和 Foo 跟前面的定义是一样的,只是新增了 Bar,它继承自 Foo。先让我们看看使用:

我们发现,Bar 没有 prop 这个属性,但是有 my_prop 这个属性,这是为什么呢?

原来,当我们定义 class Bar(Foo) 时,Python 会首先在当前类,即 Bar 中寻找 __metaclass__,如果没有找到,就会在父类 Foo 中寻找 __metaclass__,如果找不到,就继续在 Foo 的父类寻找,如此继续下去,如果在任何父类都找不到 __metaclass__,就会到模块层次中寻找,如果还是找不到,就会用 type 来创建这个类。

写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧~

小结

  • 在 Python 中,类也是一个对象。
  • 类创建实例,元类创建类。
  • 元类主要做了三件事:
    • 拦截类的创建
    • 修改类的定义
    • 返回修改后的类
  • 当你创建类时,解释器会调用元类来生成它,定义一个继承自 object 的普通类意味着调用 type 来创建它。

参考资料