Python黑魔法 元類

2021-09-19 14:22:31 字數 3414 閱讀 1606

python黑魔法:元類

術語「元程式設計」指的是程式具有編寫或操縱其自身作為它們資料的潛力。python支援稱為元類的類的元程式設計。

元類是乙個深奧的物件導向程式設計(oop)概念,隱藏在幾乎所有的python**之後。無論你是否意識到它的存在,你都一直在使用它們。大多數情況下,你並不需要了解它。而且大多數python程式設計師也很少用到,但是某些情況下你就不得不考慮使用元類。

當你有需要時,python提供了一種不是所有物件導向語言都支援的功能:你可以深入了解其內部並自定義元類。使用定製元類經常會存在爭議,正如python大咖,創作了python之禪的蒂姆·彼得斯所言:

「元模擬99%的使用者所憂慮的東西具有更深的魔法。如果你猶豫考慮是否需要它們,那麼實質上你不會需要它們(實際需要它們的人確信他們確實需要,並且不需要進行任何解釋)。「 —— 蒂姆·彼得斯

眾多pythonistas(即python發燒友所熟知的python大咖)認為你永遠不應該使用自定義元類。這樣說可能會有點極端,但大部分情況下自定義元類並不是必需的。如果乙個問題不是很明顯是否需要它們,那麼如果以一種更簡單的方式解決問題,**可能會更乾淨,更具有可讀性。

儘管如此,理解python元類還是很有必要,因為它可以更好地理解python類的內部實現。你永遠不知道:你可能有一天會發現自己處於這樣一種情況,即你確切明白自定義元類就是你想要的。

舊式類vs新式類

在python範疇,乙個類可以是兩種型別之一。官方術語並沒有對此進行確認,所以它們被非正式地稱為舊式類和新式類。

舊式類對於舊式類,類(class)和型別(type)並不完全相同。乙個舊式類的例項總是繼承自乙個名為instance的內建型別。如果obj是舊式類的例項,那麼obj.class就表示該類,但type(obj)始終是instance型別。以下示例來自python 2.7:

新式類新式類統一了類(class)和型別(type)的概念。如果obj是新式類的例項,type(obj)則與obj.class相同:

型別(type)和類(class)

在python 3中,所有類都是新式類。因此,python 3可以交換乙個引用物件的型別和類。

注意:在python 2中,預設所有類都是舊式類。在python 2.2之前,根本不支援新式類。從python 2.2開始,可以建立新式類,但必須明確宣告它為新式類。

請記住,在python中,一切都是物件。類也是物件。所以乙個類(class)必須有乙個型別(type)。那麼類的型別是什麼呢?

考慮下面的**:

x的型別,正如你所想的,是類foo,但foo的型別,即類本身是type。一般來說,任何新式類的型別都是type。

您熟悉的內建類的型別也是type:

就此而言,type的型別也是type(是的,確實如此):

type是乙個元類,任何類都是它的例項。就像乙個普通的物件是乙個類的例項一樣,python中的任何新式類以及python 3中的任何類都是type元類的乙個例項。

綜上所述:

動態定義類

內建type()函式在傳遞了乙個引數時將返回乙個物件的型別。對於新式類,通常與物件的class屬性相同:

你也可以傳遞三個引數type(, , )呼叫type():

以這種方式呼叫type()將建立乙個type元類的新例項。換句話說,它動態地建立了乙個新的類。

在下面每個示例中,前面的**片段使用type()動態地定義了乙個類,後面的**片斷使用常用的class語句定義了類。在每種情況下,這兩個**片段在功能上是一樣的。

示例1在第乙個示例中,傳遞給type()的引數和都是空的,沒有指定任何父類的繼承,並且初始在命名空間字典中沒有放置任何內容。這或許是最簡單的類的定義:

示例2這裡,是乙個具有單個元素foo的元組,指定了bar繼承的父類。乙個名為attr的屬性最初放置在命名空間字典中:

示例3這一次,又是空的。兩個物件通過引數放置在命名空間字典中。第乙個是屬性attr,第二個是函式attr_val,該函式將成為已定義類的乙個方法:

示例4上面僅用python中的lambda定義乙個非常簡單的函式。在下面的例子中,外部先定義了乙個稍微複雜的函式f,然後在命名空間字典中通過函式名f分配給attr_val:

自定義元類

重新思考一下先前的這個例子:

表示式foo()建立乙個新的類foo的例項。當直譯器遇到foo(),將按一下順序進行解析:

如果foo沒有定義new()和init(),那麼將呼叫foo父類的預設方法。但是如果foo定義這些方法,就會覆蓋來自父類的方法,這就允許在例項化foo時可以自定義行為。

在下面的**中,定義了乙個自定義方法new(),並將它賦值給foo的new()方法:

這會修改類foo的例項化行為:每次foo建立例項時,預設情況下都會將名為attr的屬性進行初始化,將該屬性設定為100。(類似於這樣的**通常會出現在init()方法中,不會出現在new()方法裡,這個例子僅為演示目的而設計。)

現在,正如前面重申的那樣,類也是物件。假設你想類似地在建立類foo時自定義例項化行為。如果你要遵循上面的模式,則需要再次定義乙個自定義方法,並將其指定為類foo的例項的new()方法。foo是type元類的乙個例項,所以**如下所示:

阿偶,你可以看到,不能重新指定元類type的new()方法。python不允許這樣做。

可以這麼講,type是派生所有新式類的元類。無論如何,你真的不應該去修改它。但是,如果你想自定義乙個類的例項化,那麼有什麼辦法呢?

一種可能的解決方案是自定義元類。本質上,不是去試圖修改type元類,而是定義自己派生於type的元類,然後對其進行修改。

第一步是定義派生自type的元類,如下:

頭部定義class meta(type):指定了meta派生自type。既然type是元類,那meta也是乙個元類。

請注意,重新自定義了meta的new()方法。因為不可能直接對type元類進行此類操作。new()方法執行以下操作:

現在實現**的另一半:定義乙個新類foo,並指定其元類為自定義元類meta,而不是標準元類type。可以通過在類定義中使用關鍵字metaclass完成,如下所示:

瞧!foo已經自動擁用了從meta元類的屬性attr。當然,你定義的任何其他類也會如此:

就像乙個類作為建立物件的模板一樣,乙個元類可以作為建立類的模板。元類有時被稱為類工廠。

比較以下兩個示例:

物件工廠:

類工廠:

真的是必要的嗎?

就像上面的類工廠的例子一樣簡單,它是metaclasses如何工作的本質。它們允許定製類的例項化。

儘管如此,僅僅為了賦予每個新建立的類的自定義屬性attr,確實有點小題大做。你真的需要乙個metaclass來實現嗎?

在python中,至少有其他一些方法可以實現同樣的效果:

簡單的繼承:

類裝飾器:

結論正如蒂姆·彼得斯建議的,元類可以很容易地作為一種「尋找解決問題的方案」,通常不需要建立自定義元類。如果手頭上的問題能夠以更簡單的方式解決,那或許就應該採用。儘管如此,了解元類有助於理解python的類,並能夠識別元類是否是工作中真正適合使用的工具。

python 黑魔法之編碼轉換

我們在使用其他語言的庫做編碼轉換時,對於無法理解的字元,通常的處理也只有兩種 或三種 但是在複雜的現實世界中,由於各種不靠譜,我們處理的文字總會出現那麼些不和諧因素,比如混合編碼。在這種情況下,又回到了上面的處理辦法。那麼問題來了,python有沒有更好地辦法呢?答案是,有!python的編碼轉換流...

python 黑魔法之 編碼轉換

我們在使用其他語言的庫做編碼轉換時,對於無法理解的字元,通常的處理也只有兩種 或三種 但是在複雜的現實世界中,由於各種不靠譜,我們處理的文字總會出現那麼些不和諧因素,比如混合編碼。在這種情況下,又回到了上面的處理辦法。那麼問題來了,python有沒有更好地辦法呢?答案是,有!python的編碼轉換流...

巨集定義黑魔法

1 define clr red 033 31m 紅色字 define clr green 033 32m 綠色字 define clr yellow 033 33m 黃色字 define clr blue 033 34m 藍色字 define clr purple 033 35m 紫色字 defi...