Python的metaclass

發表於2016-06-24

metaclass一般譯作元類,它是一個類的類(型)。簡單來說,元類的例項是一個類,而這個類的類就是元類了。
也就是說,相對於類可以在執行時動態構造物件而言,元類也可以在執行時動態生成類。在C++等語言中,要得知一個物件的類是很麻煩的(MFC是用巨集來實現的);而Python由於有自省功能,所以非常簡單。

先不說怎麼定義元類,直接看怎麼用:

再來看個更好玩的例子:

這裡的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物件:

結果:

3
7
<type ‘classobj’>
True

至於type物件和class物件有什麼區別,似乎沒必要區分。在Python中,類和型別是一致的。

我們還能繼承type:

輸出:

3
7
<type ‘type’>
<class ‘__main__.CalculatorType’>
True

而且type物件裡也能定義其他的方法,不過這些方法的第一個引數不再是self了,而是cls。因為呼叫時是用生成的class的例項來呼叫的:

結果:

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__屬性,當然你也可以用函式物件。

但是它有什麼用呢?這裡就演示一下:

可以看到,Calculator完全沒有定義任何方法,卻可以使用它的metaclass中定義的方法。
它實際上是之前的語法的簡化(語法糖),輸出結果是完全相同的。
class變成了語法糖,可能是你不敢相信的,不過它就是如此,並且你應該掌握之前的語法,才能更好地理解這個過程。

至於元類和基類的不同之處,強烈建議你看《Python 中的元類程式設計,第 2 部分》這篇文章,我懶得寫程式碼了。

不過說了半天,仍沒說出元類究竟有什麼用。實際上元類能實現的,基本上都能用其他方法實現,只是元類提供了一種更高層次的抽象,可以用於實現面向方面程式設計(AOP)。
先介紹下AOP。當要完成一件事時,我們需要即關注其主要任務,又關注其次要任務(例如支援多執行緒、資料需要加解密等)。
在一般的實現中,這2類任務是耦合的,所以當次要任務變化時,整個框架都要進行很大的調整。而AOP則是將這2類任務解耦合的一種思想。
舉例來說,當我們寫多執行緒的程式時,訪問共享的物件需要加鎖和解鎖,這2種操作明顯屬於次要任務;但你寫程式時,一般都是加鎖、訪問物件、解鎖這3個動作寫在一起的。如果能讓訪問物件時自動加鎖,訪問完自動解鎖,而且這個操作並不寫在我們的訪問函式或被訪問物件的方法裡,那就會變得很方便了。而且假如今後的訪問還需要加密解密之類的操作,我們也無需改寫我們的函式或被訪問物件,只需更改自動呼叫的那個方法即可。

要實現這個目標,Python為我們提供了2種辦法:
一種是使用Decorator,裝飾那個訪問函式,新增加解鎖的操作;
另一種是使用元類,在元類中包裝訪問函式。實現程式碼可以參考《利用metaclass實現python的aop》

相關文章