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》。