1. 程式人生 > >python元類

python元類

日期: 16 September, 2008.

“元類的魔幻變化比 99% 的使用者所擔心的更多,當你搞不懂是否真的需要用它的時候,就是不需要。”

—Tim Peters

元類被稱為 Python 中的“深奧的巫術”。儘管你需要用到它的地方極少(除非你基於 zope 程式設計),可事實上它的基礎理論其實令人驚訝地易懂。

一切皆物件

  • 一切皆物件
  • 一切都有型別
  • class ”和“ type ”之間本質上並無不同
  • 類也是物件
  • 它們的型別是 type

以前,術語 type 用於內建型別,而術語 class 用於使用者定義的類,但自 Pythoon 2.2

以來“ class ”和“ type ”本質上並無不同。

對於舊風格( old-style )類的型別是 types.ClassType

真的,這是真的

Python 2.5.1 ( r251 :54869, Apr 18 2007, 22:08:04) >>> class Something ( object ): ... pass ... >>> Something < class '__main__.Something' > >>> type ( Something ) <

type 'type' >

從這裡可以看出在互動式直譯器中建立的類是一個 first class 的物件。

類的類是……

它的元類……

就像物件是類的例項一樣,類是它的元類的例項。

呼叫元類可以建立類。

確切來說, Python 中的其它物件也是如此。

因此當你建立一個類時……

直譯器會呼叫元類來生成它……

定義一個繼承自 object 的普通類意味著呼叫 type 來建立它:

>>> help(type)

Help on class type in module __builtin__:

class type(object)

 | type(object) -> the object's type

 | type(name, bases, dict) -> a new type

type 的第二種用法尤為重要。當 Python 直譯器在執行一條類定義語句時(如例子中最初的兩行程式碼之後),它會用下面的引數呼叫 type

  • 字串形式的類名
  • 元組形式的基類序列——在我們的例子中是隻有一個元素的元組( ’one-pl’ [1] ,如 (object,)
  • 包括由名字影射的類成員(類屬性、方法等)的字典

簡單模擬

>>> def __init__ ( self ): ... self . message = 'Hello World' ... >>> def say_hello ( self ): ... print self . message ... >>> attrs = { '__init__' : __init__ , 'say_hello' : say_hello } >>> bases = ( object ,) >>> Hello = type ( 'Hello' , bases , attrs ) >>> Hello < class '__main__.Hello' > >>> h = Hello () >>> h . say_hello () Hello World

以上程式碼建立了類屬性的字典,然後呼叫 type 來建立了名為 Hello 的類。

__metaclass__ 的魔法

只要在類定義中把 __metaclass__ 設定為任意有著與 type 相同引數的可呼叫物件,就能夠提供自定義的元類。

通常使用從 type 繼承的方法:

class PointlessMetaclass ( type ): def __new__ ( meta , name , bases , attrs ): # do stuff... return type . __new__ ( meta , name , bases , attrs )

重要的是在 __new__ 方法中我們能夠讀取或改變傳入的用以建立新類的引數。從而能夠內省屬性字典和改動、增加或者刪除成員。

儘管當例項化一個類時這兩個函式都會被呼叫,但覆蓋 __new__ __init__ 更為重要。 __init__ 初始化一個例項,而 __new__ 的職責是建立它。因此如果元類用以自定義類的建立,就需要覆蓋 type __new__

使用新類而非僅僅提供工廠函式的原因在於如果使用工廠函式(那樣只是呼叫 type )的話元類不會被繼承。

>>> class WhizzBang ( object ): ... __metaclass__ = PointlessMetaclass ... >>> WhizzBang < class '__main__.WhizzBang' > >>> type ( WhizzBang ) < class '__main__.PointlessMetaClass' >

WhizzBang 是一個類,但它現在已經不是 type 的例項,而是我們自定義的元類的例項了……

這有什麼用?

很好的問題,元類將用在建立使用了它的新類時呼叫,這裡是一些關於這樣做的好處的觀點:

  • 裝飾( Decorate )類的所有方法,用以日誌記錄或者效能剖分。
  • 自動 Mix-in 新方法
  • 在建立時註冊類。(例如自動註冊外掛或從類成員建立資料庫模式。)
  • 提供介面註冊,功能自動發現和介面適配。
  • 類校驗:防止子類化,校驗所有的方法是否都有 docstrings

最重要之處在於元類中是在最後對 type 的呼叫時才真正建立類,所以可以自由地隨你喜歡地改變屬性字典(以及名稱和元組形式的基類序列)。

一些流行的 Python ORM Object Relational Mappers (物件關係影射),用以和資料庫協同工作)也如此使用元類。

哦,還有因為元類是繼承的,所以你能夠提供一個使用了你的元類的基類,而繼承自它的子類就無需顯式宣告它了。

但是……

我曾未需要使用它來編寫程式碼……(我們用它來剖分,也在 專案廣泛應用它,但我不編寫這些)。

還有,這一切只適用於 Python 2.x ,其中的機制在 Python 3 中已經改變了。

type(type) is type

Python 2.6 中現在也可用使用 來實現許多以前可能需要用元類來實現的東西。

最後,還有一個極盡奇技淫巧的例子(稍為深入,但仍然不難消化),可以去看看 。它通過位元組碼和方法簽名重寫來避免顯式地宣告 self