metaclass一般译作元类,它是一个类的类(型)。简单来说,元类的实例是一个类,而这个类的类就是元类了。
也就是说,相对于类可以在运行时动态构造对象而言,元类也可以在运行时动态生成类。在C++等语言中,要得知一个对象的类是很麻烦的(MFC是用宏来实现的);而Python由于有自省功能,所以非常简单。

先不说怎么定义元类,直接看怎么用:

12345678910 >>> type(1) # type函数会返回对象的类,所以type(1)拿到的是整型<type \’int\’>>>> type(1)(20) # 然后生成整型这个类的一个对象20>>> type(type(1)) # 现在取的是整型这个类的类,也就是元类了。由于它没定义元类,所以拿到的是默认的元类type。<type \’type\’>>>> type(type(1))(1) # 再将1传给我们拿到的元类,实际上就是type(1),于是再次拿到整型<type \’int\’>>>> type(type(1))(1)(20) # 再用这个整型生成对象20

再来看个更好玩的例子:

12345678910111213141516171819202122 Calculator = type(\’Calculator\’, (), {\’add\’: lambda self, x, y: x + y, \’sub\’: lambda self, x, y: x y}) calc = Calculator()print calc.add(1, 2)print calc.sub(10, 3) print type(Calculator)print isinstance(calc, object) print AdvancedCalculator = type(\’AdvancedCalculator\’, (Calculator,), {\’mul\’: lambda self, x, y: x * y, \’div\’: lambda self, x, y: x / y}) calc2 = AdvancedCalculator()print calc2.add(5, 6)print calc2.sub(7, 4)print calc2.mul(2, 6)print calc2.div(6, 3) print type(AdvancedCalculator)print isinstance(calc2, object)print isinstance(calc2, Calculator)

这里的type接收3个参数,第一个参数是类名,第二个是父类(由于允许多重继承,所以是个元组,空元组表示父类为object),第三个参数为类的成员字典。它会返回一个新风格的type对象,这个对象实际上就是一个动态生成的类。
所以Calculator就是一个父类为object的类,并且它有add和sub这2个方法。之后我又让AdvancedCalculator继承了它,并添加了mul和div这2个方法。
输出结果:

3
7

True

11
3
12
2

True
True

当然,type并不是唯一可以生成类的方法,new.classobj也可以实现相同的功能,不过它返回的不是新风格的type对象,而是新风格的class对象:

123456789 from new import classobj Calculator = classobj(\’Calculator\’, (), {\’add\’: lambda self, x, y: x + y, \’sub\’: lambda self, x, y: x y})calc = Calculator()print calc.add(1, 2)print calc.sub(10, 3) print type(Calculator)print isinstance(calc, object)

结果:

3
7

True

至于type对象和class对象有什么区别,似乎没必要区分。在Python中,类和类型是一致的。

我们还能继承type:

1234567891011121314151617 class CalculatorType(type):  # 实际上CalculatorType的内容可以为pass,就会继承type的方法,这里只是演示如何覆盖type的方法  def __new__(cls, name, bases, dct):    return type.__new__(cls, name, bases, dct)   def __init__(cls, name, bases, dct):    super(CalculatorType, cls).__init__(name, bases, dct) Calculator = CalculatorType(\’Calculator\’, (), {\’add\’: lambda self, x, y: x + y, \’sub\’: lambda self, x, y: x y}) calc = Calculator()print calc.add(1, 2)print calc.sub(10, 3) print type(CalculatorType)print type(Calculator)print isinstance(calc, object)

输出:

3
7


True

而且type对象里也能定义其他的方法,不过这些方法的第一个参数不再是self了,而是cls。因为调用时是用生成的class的实例来调用的:

12345678910111213 class CalculatorType(type):  def add(cls, x, y):    return x + y   def sub(cls, x, y):    return x y Calculator = CalculatorType(\’Calculator\’, (), {}) print Calculator.add(1, 2)print Calculator.sub(10, 3) print CalculatorType.add(1, 2)

结果:

3
7
Traceback (most recent call last):
File “live.py”, line 13, in
print CalculatorType.add(1, 2)
TypeError: unbound method add() must be called with CalculatorType instance as first argument (got int instance instead)

可以看到,CalculatorType里并没绑定add这个方法(因为不是通过它的实例来调用的),而在它生成的Calculator类(即CalculatorType的实例)里却有。

接着就说下如何定义metaclass了,方法很简单:
在Python 2.2之后,可以直接在类定义时对__metaclass__这个属性进行赋值。
在Python 3.0之后,可以在类的父类参数中加上metaclass参数来设置。
还有一点,metaclass是一个接受三个参数(class的名字,基类tuple,class 的属性字典)的callable对象,并且它返回一个callable对象。别忘了元类本身就是这样一个callable对象(例如type),所以可以直接设置将元类名赋值给__metaclass__属性,当然你也可以用函数对象。

但是它有什么用呢?这里就演示一下:

123456789101112 class CalculatorType(type):  def add(cls, x, y):    return x + y   def sub(cls, x, y):    return x y class Calculator:  __metaclass__ = CalculatorType print Calculator.add(1, 2)print Calculator.sub(10, 3)

可以看到,Calculator完全没有定义任何方法,却可以使用它的metaclass中定义的方法。
它实际上是之前的语法的简化(语法糖),输出结果是完全相同的。
class变成了语法糖,可能是你不敢相信的,不过它就是如此,并且你应该掌握之前的语法,才能更好地理解这个过程。

至于元类和基类的不同之处,强烈建议你看《Python 中的元类编程,第 2 部分》这篇文章,我懒得写代码了。

不过说了半天,仍没说出元类究竟有什么用。实际上元类能实现的,基本上都能用其他方法实现,只是元类提供了一种更高层次的抽象,可以用于实现面向方面编程(AOP)。
先介绍下AOP。当要完成一件事时,我们需要即关注其主要任务,又关注其次要任务(例如支持多线程、数据需要加解密等)。
在一般的实现中,这2类任务是耦合的,所以当次要任务变化时,整个框架都要进行很大的调整。而AOP则是将这2类任务解耦合的一种思想。
举例来说,当我们写多线程的程序时,访问共享的对象需要加锁和解锁,这2种操作明显属于次要任务;但你写程序时,一般都是加锁、访问对象、解锁这3个动作写在一起的。如果能让访问对象时自动加锁,访问完自动解锁,而且这个操作并不写在我们的访问函数或被访问对象的方法里,那就会变得很方便了。而且假如今后的访问还需要加密解密之类的操作,我们也无需改写我们的函数或被访问对象,只需更改自动调用的那个方法即可。

要实现这个目标,Python为我们提供了2种办法:
一种是使用Decorator,装饰那个访问函数,添加加解锁的操作;
另一种是使用元类,在元类中包装访问函数。实现代码可以参考《利用metaclass实现python的aop》。