天天看點

python元類Metaclass

高票回答一

在了解元類之前,需要掌握python的類。python從Smalltalk程式設計語言中借鑒了非常特殊的類的概念。

在大多數程式設計語言中,類隻是描述如何建立對象的代碼片段,這在python中也是成立的:

但是類在python中又不僅僅如此,類也是對象。

隻要你使用了關鍵字​<code>​class​</code>​,python就會去執行它并且建立一個對象。

上面這段代碼在記憶體中建立名為ObjectCreator的對象。

這個對象(類)擁有建立對象(執行個體)的能力,是以它是一個類。

但是它也仍然是一個對象,是以:

你可以把它指派給一個變量

可以拷貝它

可以給它添加屬性

可以把它作為函數參數傳遞

比如:

既然類是對象,那麼可以像任意對象那樣動态地建立它。

首先,可以在一個函數中使用​<code>​class​</code>​關鍵字建立類

但是因為還需要自己去寫整個類,是以這個不是那麼動态。

既然類是對象,那麼他們一定可以使用某些東西生成。

當你使用​<code>​class​</code>​關鍵字,python自動建立一個對象,但是和python中大多數的事情一樣,它也提供了手動實作的方法。

​<code>​type​</code>​是一個可以讓你知道對象類型的函數

​<code>​type​</code>​擁有完全不同的能力,它也可以動态地建立類,​<code>​type​</code>​也可以接收類的描述作為參數傳回一個類。

(某些函數根據參數的不同而擁有完全不同的用途是有點傻,但這是python向後相容導緻的問題)

​<code>​type​</code>​是這樣工作的:

在這個段代碼中:

​<code>​name​</code>​: 類命

​<code>​bases​</code>​: 父類元組(為了繼承關系,可以為空)

​<code>​attrs​</code>​: 包含屬性名和值的字典

可以用下面的方法手動建立:

在這裡使用​<code>​MyShinyClass​</code>​作為類命和儲存類引用的變量,它們可以不同,但是沒必要複雜化。

​<code>​type​</code>​接受字典去定義類的屬性,是以

可以寫作:

可以像普通類那樣使用

當然也可以繼承它:

如果想要給類添加方法,可以定義一個擁有合适簽名的函數并将它作為屬性指派給類

甚至可以像給普通建立的類對象添加方法那樣,可以在動态地建立類之後,給類添加更多的方法。

到這裡你可以明白:在python中,類是對象,可以動态地建立類。

這就是當你使用​<code>​class​</code>​關鍵字時python的行為,它是使用元類來實作的。

元類是建立類的東西。我們為了建立對象而定義類,但是在python中類也是對象。是以,元類就是建立這些對象的東西。它們是類的類。

你已經看過了​<code>​type​</code>​允許你做的操作:

是因為​<code>​type​</code>​其實是一個元類,​<code>​type​</code>​是python在幕後用來建立所有類的元類。

你可能會疑惑它為什麼是小寫的,為什麼不寫作Type?

我猜是為了保持一緻性,str是建立strings對象的類,int是建立integer對象的類,type是建立類對象的類。你可以通過​<code>​__class__​</code>​屬性來檢視。

python中的一切都是對象,包括整型,字元串,函數和類。所有的都是對象,并且它們的都是被類建立的。

那麼__class__的__class__是什麼呢?

是以元類隻是建立類對象的東西,如果你想也可以叫它類工廠(class factory),​<code>​type​</code>​是python内置的元類,當然也可以建立自己的元類。

在python2中,當你寫一個類實作代碼的時候可以添加一個​<code>​__metaclass__​</code>​屬性。

如果你像上面這樣做,python使用這個元類來建立類​<code>​Foo​</code>​。小心,這種方式很棘手。你先寫下​<code>​class Foo(object)​</code>​,但是類對象​<code>​Foo​</code>​還沒有在記憶體中被建立。

python會在類定義中查找​<code>​__metaclass__​</code>​,如果找到,他就會用元類來建立對象類​<code>​Foo​</code>​,如果沒有,使用​<code>​type​</code>​來建立類。

當執行上面的代碼的時候,python執行以下操作:

先查找​<code>​Foo​</code>​有沒有​<code>​__metaclass__​</code>​屬性。如果有,使用​<code>​__metaclass__​</code>​在記憶體中建立一個類對象。如果找不到​<code>​__metaclass__​</code>​,将在子產品級别查找​<code>​__metaclass__​</code>​,然後嘗試做同樣的操作(但僅限于不繼承任何東西的類,基本是舊式類)。

如果它找不到任何的​<code>​__metaclass__​</code>​,将會使用​<code>​bar​</code>​(第一個父類)的元類(可能會是預設的​<code>​type​</code>​)來建立類對象。

要小心​<code>​__metaclass__​</code>​屬性不會被繼承,父類的元類(​<code>​Bar.__class__​</code>​)會被繼承。如果​<code>​Bar​</code>​使用了​<code>​__metaclass__​</code>​屬性來建立一個帶有​<code>​type()​</code>​方法(而不是​<code>​type.__new__​</code>​)的​<code>​Bar​</code>​,子類将不會繼承這個行為。

現在問題是可以在__metaclass__裡放進什麼?答案就是可以建立類的東西。那什麼可以建立類呢?​<code>​type​</code>​,或者任何它的子類或者使用它的東西。

設定元類的文法在python3中已被改變

比如,​<code>​__metaclass__​</code>​屬性不再使用,而是作為基類清單的關鍵字參數。但是元類的行為基本保持不變。

python3中的元類新增的是你也可以使用關鍵字參數給元類傳遞屬性,比如:

下面的内容将講述python是如何處理的

元類的主要目的是在建立類的時候自動地去改變類。

通常是為了建立比對目前上下文的類的API這樣做。

想象你決定在你的子產品中的所有的類的屬性都要大寫。有幾種方法可以實作,但是其中一種是設定一個子產品級别的​<code>​__metaclass__​</code>​。用這種方法,這個子產品的所有類都使用這個元類建立,我們隻需要告訴這個元類把所有的屬性轉成大寫即可。

幸運得,​<code>​__metaclass__​</code>​可以是任何可調用的對象,它不需要是一個普通類。

是以,我們可以用一個函數來開始一個簡單的例子

可以驗證一下:

現在我們使用一個真正的類作為元類來實作同樣的功能

現在我們知道了他們的含義,讓我們用更短更現實的變量名來重寫上面的方法

你可能已經注意到了額外的參數​<code>​cls​</code>​,它沒什麼特殊的:​<code>​__new__​</code>​始終接收定義它的類作為第一個參數,就像普通方法的​<code>​self​</code>​參數,它接受執行個體作為第一個參數,或者作為類方法的時候接受定義它的類作為第一個參數。

但是這不是合适的面向對象OOP思想。我們可以直接調用​<code>​type​</code>​,并且不重寫或者調用父類的​<code>​__new__​</code>​:

使用super可以讓它更清晰明了,它将會簡化繼承(因為你當然可以從type中繼承,從元類中繼承,進而擁有元類)

在python3中,如果你像下面這樣使用關鍵字參數調用

它在元類中會轉化成這樣去使用:

之是以使用元類的代碼這麼複雜不是因為元類,而是因為通常你使用元類去操作依賴于内省,操縱繼承,變量如​<code>​__dict__​</code>​等等的扭曲的事情上

确實元類在做這些黑魔法操作上特别有用,是以才會有這麼複雜的東西,但是他們本身是很簡單的:

攔截類的建立

修改類

傳回修改後的類

既然​<code>​__metaclass__​</code>​接受任何可調用對象,既然使用類明顯得更複雜為什麼還要使用類?

下面是這麼做的幾個原因:

攔截更清晰,當你去看​<code>​UpperAttrMetaclass(type)​</code>​的時候,你知道接下來會發生什麼

可以使用面像對象思想OOP,元類可以從元類繼承,重寫父類方法,元類甚至可以使用元類

如果你指定了一個一個元類類,不是元類函數,類的子類将會是元類的執行個體

可以使你的代碼更加的結構化,你不會像上面的例子一樣使用元類做一些瑣碎的小事。它通常用于更複雜的功能。擁有建立幾個方法并且組織他們在一個類中的能力對代碼的易讀性是非常有用的。

你可以執着于​<code>​__new__​</code>​,​<code>​__init__​</code>​和​<code>​__call__​</code>​這些允許你做不同操作的函數,即使通常你可以全部在​<code>​__new__​</code>​中實作,也有一些人更樂意使用​<code>​__init__​</code>​.

他們被稱為元類一定意味着什麼。

問題是為什麼要用一些隐晦的容易出錯的特性呢?

其實通常我們不用。

元類是更深奧的魔法,99%的使用者都不需要擔心它。如果你不确定你是否需要它們,那麼你就不需要(需要它們的人一定很确定他們需要元類并且不需要解釋) _Python Guru Tim Peters

使用元類的主要應用場景是建立API。一個典型的例子就是Django的ORM,它允許你像下面這樣去定義:

但是如果你這樣做:

它不會傳回一個​<code>​IntegerField​</code>​對象,而是傳回一個​<code>​int​</code>​,甚至可以直接從資料庫中擷取。

這可能是因為​<code>​models.Model​</code>​定義了​<code>​__metaclass__​</code>​,它使用了一些魔法将你使用簡單語句定義的​<code>​Person​</code>​轉化為連接配接到資料庫字段的複雜鈎子。

Django通過暴露一個簡單的API和使用元類,從這個API中重建代碼來完成幕後的實際操作,使複雜的東西看起來簡單。

首先,你知道類是可以建立執行個體的對象。

實際上類就是他們本身的執行個體,元類的執行個體。

python中的一切都是對象,它們都要麼是類的執行個體,要麼是元類的執行個體。除了​<code>​type​</code>​。

type其實是它自己的元類。它不是你可以在純python中重制的東西,它是在實作級别上做了一些欺騙才實作的。

其次,元類很複雜,你可能不像用它們來做很簡單的類修改,你可以通過下面兩個不同的技術來改變類:

猴子更新檔

類裝飾器

99%的情況,你最好使用這些來改變類。

但是98%的情況你完全不需要修改類。

高票回答二

元類是類的類,一個類定義了類的執行個體的行為,同樣一個元類定義了類的行為。類是元類的執行個體。

在python中可以為元類使用任意的可調用對象,但是更好的實作是讓它成為一個真正的類。​<code>​type​</code>​是python的常見元類,​<code>​type​</code>​本身是一個類,并且是它自己的類型。雖然在python中不能完全重制類似​<code>​type​</code>​的東西,但是python裡還是有些小技巧,為了建立自己的元類,隻需要繼承​<code>​type​</code>​。

一個元類最常用作類工廠(class-factory),當你通過調用類來建立對象的時候,python通過調用元類來建立一個新類(當執行class語句的時候)。通過​<code>​__init__​</code>​和​<code>​__new__​</code>​方法的結合,元類允許你在建立一個一個類的時候做一些額外的操作,比如用一些注冊資訊來注冊類,或者使用其他東西完全取代類。

當執行​<code>​class​</code>​語句(class statement)的時候,python首先像執行普通的代碼塊那樣去執行​<code>​class​</code>​語句體。結果命名空間(一個字典)暫存了這個即将生成的類(class-to-be)的屬性。元類是通過查找這個即将生成的類(元類被他繼承)的​<code>​__metaclass__​</code>​屬性或者​<code>​__metaclass__​</code>​全局變量來決定的。然後使用此類類命,基類,屬性調用元類來執行個體化它。

然而,元類實際定義了類的類型,而不僅僅是類工廠,是以可以使用元類來做的事情很多,比如,可以在元類上定義正常方法,這些元類方法就類似于類方法,可以在不執行個體化類的時候被調用,但是又不像類方法,他們不能被類的執行個體對象調用. ​<code>​type.__subclasses__()​</code>​就是一個在type元類裡這樣的方法的例子。同樣也可以定義正常魔術方法,比如​<code>​__add__​</code>​,​<code>​__iter__​</code>​,​<code>​__getattr__​</code>​來實作或者改變類行為。

下面是一些零碎的例子: