metaclass一般译作元类,它是一个类的类(型)。简单来说,元类的实例是一个类,而这个类的类就是元类了。
也就是说,相对于类可以在运行时动态构造对象而言,元类也可以在运行时动态生成类。在C++等语言中,要得知一个对象的类是很麻烦的(MFC是用宏来实现的);而Python由于有自省功能,所以非常简单。
先不说怎么定义元类,直接看怎么用:
1 2 3 4 5 6 7 8 9 10 |
>>> 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 |
再来看个更好玩的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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
<type ‘type’>
True
11
3
12
2
<type ‘type’>
True
True
当然,type并不是唯一可以生成类的方法,new.classobj也可以实现相同的功能,不过它返回的不是新风格的type对象,而是新风格的class对象:
1 2 3 4 5 6 7 8 9 |
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
<type ‘classobj’>
True
至于type对象和class对象有什么区别,似乎没必要区分。在Python中,类和类型是一致的。
我们还能继承type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
<type ‘type’>
<class ‘__main__.CalculatorType’>
True
而且type对象里也能定义其他的方法,不过这些方法的第一个参数不再是self了,而是cls。因为调用时是用生成的class的实例来调用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 <module>
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__属性,当然你也可以用函数对象。
但是它有什么用呢?这里就演示一下:
1 2 3 4 5 6 7 8 9 10 11 12 |
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》。