python元類
日期: 16 September, 2008.
“元類的魔幻變化比 99% 的使用者所擔心的更多,當你搞不懂是否真的需要用它的時候,就是不需要。”
—Tim Peters
元類被稱為 Python 中的“深奧的巫術”。儘管你需要用到它的地方極少(除非你基於 zope 程式設計),可事實上它的基礎理論其實令人驚訝地易懂。
一切皆物件
- 一切皆物件
- 一切都有型別
- “ class ”和“ type ”之間本質上並無不同
- 類也是物件
- 它們的型別是 type
以前,術語 type 用於內建型別,而術語 class 用於使用者定義的類,但自 Pythoon 2.2
對於舊風格( 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 ) <
從這裡可以看出在互動式直譯器中建立的類是一個 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 。