自訂元類
到現(xiàn)在,我們已經(jīng)知道元類別是什麼東東了。那麼,從頭到尾我們還不知道元類到底有啥用。只是了解了一下元類。在了解它有啥用的時(shí)候,我們先來了解下怎麼自訂元類別。因?yàn)橹挥辛私饬嗽觞N自訂才能更好的理解它的作用。
首先我們來了解下__metaclass__ 屬性
metaclass,直譯為元類,簡(jiǎn)單的解釋就是:
當(dāng)我們定義了類別以後,就可以根據(jù)這個(gè)類別建立出實(shí)例,所以:先定義類,然後再建立實(shí)例。
但是如果我們想建立出類別呢?那就必須根據(jù)metaclass建立出類,所以:先定義metaclass,然後再建立類別。
連接起來就是:先定義metaclass,就可以建立類,最後再建立實(shí)例。
所以,metaclass允許你建立類別或修改類別。換句話說,你可以把類別看成是metaclass創(chuàng)建出來的「實(shí)例」。
class MyObject(object): __metaclass__ = something… […]
如果是這樣寫的話,Python 就會(huì)用元類別來建立類別 MyObject。當(dāng)你寫下 class MyObject(object),但類別物件 MyObject 還沒有在記憶體中建立。 Python 會(huì)在類別的定義中尋找 __metaclass__ 屬性,如果找到了,Python 就會(huì)用它來建立類別 MyObject,如果沒有找到,就會(huì)用內(nèi)建的 type 函數(shù)來建立這個(gè)類別。如果還不怎麼理解,看下下面的流程圖:
再舉個(gè)實(shí)例:
class Foo(Bar): pass
它的判斷流程是怎麼樣的呢?
首先判斷 Foo 中是否有 __metaclass__ 這個(gè)屬性?如果有,Python 會(huì)在記憶體中透過 __metaclass__ 建立一個(gè)名字為 Foo 的類別物件(注意,這裡是類別物件)。如果 Python 沒有找到__metaclass__ ,它會(huì)繼續(xù)在 Bar(父類)中尋找__metaclass__ 屬性,並嘗試做和前面相同的操作。如果 Python在任何父類別中都找不到 __metaclass__ ,它就會(huì)在模組層次中去尋找 __metaclass__ ,並嘗試做同樣的操作。如果還是找不到 __metaclass__ ,Python 就會(huì)用內(nèi)建的 type 來建立這個(gè)類別物件。
其實(shí) __metaclass__ 就是定義了 class 的行為。類似於 class 定義了 instance 的行為,metaclass 則定義了 class 的行為??梢哉f,class 是 metaclass 的 instance。
現(xiàn)在,我們基本上了解了 __metaclass__ 屬性,但是,也沒講過如何使用這個(gè)屬性,或者說這個(gè)屬性可以放些什麼?
答案就是:可以創(chuàng)建一個(gè)類別的東西。那什麼可以用來創(chuàng)建一個(gè)類別呢? type,或任何使用到 type 或子類化 type 的東東都可以。
元類別的主要目的就是為了當(dāng)建立類別時(shí)能夠自動(dòng)地改變類別。通常,你會(huì)為API 做這樣的事情,你希望可以建立符合目前上下文的類別。假想一個(gè)很愚蠢的例子,你決定在你的模組裡所有的類別的屬性都應(yīng)該是大寫形式。有好幾種方法可以辦到,但其中一種就是透過在模組層級(jí)設(shè)定__metaclass__ 。採(cǎi)用這種方法,這個(gè)模組中的所有類別都會(huì)透過這個(gè)元類別來創(chuàng)建,我們只需要告訴元類別把所有的屬性都改成大寫形式就萬(wàn)事大吉了。
幸運(yùn)的是,__metaclass__ 實(shí)際上可以被任意調(diào)用,它並不需要是一個(gè)正式的類別。所以,我們?cè)谶@裡就先以一個(gè)簡(jiǎn)單的函數(shù)作為例子開始。
# 元類會(huì)自動(dòng)將你通常傳給‘type’的參數(shù)作為自己的參數(shù)傳入 def upper_attr(future_class_name, future_class_parents, future_class_attr): '''返回一個(gè)類對(duì)象,將屬性都轉(zhuǎn)為大寫形式''' # 選擇所有不以'__'開頭的屬性 attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) # 將它們轉(zhuǎn)為大寫形式 uppercase_attr = dict((name.upper(), value) for name, value in attrs) # 通過'type'來做類對(duì)象的創(chuàng)建 return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # 這會(huì)作用到這個(gè)模塊中的所有類 class Foo(object): # 我們也可以只在這里定義__metaclass__,這樣就只會(huì)作用于這個(gè)類中 bar = 'bip' print hasattr(Foo, 'bar') # 輸出: False print hasattr(Foo, 'BAR') # 輸出:True f = Foo() print f.BAR # 輸出:'bip' 用 class 當(dāng)做元類的做法: # 請(qǐng)記住,'type'實(shí)際上是一個(gè)類,就像'str'和'int'一樣 # 所以,你可以從type繼承 class UpperAttrMetaClass(type): # __new__ 是在__init__之前被調(diào)用的特殊方法 # __new__是用來創(chuàng)建對(duì)象并返回之的方法 # 而__init__只是用來將傳入的參數(shù)初始化給對(duì)象 # 你很少用到__new__,除非你希望能夠控制對(duì)象的創(chuàng)建 # 這里,創(chuàng)建的對(duì)象是類,我們希望能夠自定義它,所以我們這里改寫__new__ # 如果你希望的話,你也可以在__init__中做些事情 # 還有一些高級(jí)的用法會(huì)涉及到改寫__call__特殊方法,但是我們這里不用 def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return type(future_class_name, future_class_parents, uppercase_attr)
但是,這種方式其實(shí)不是 OOP。我們直接呼叫了 type,而且我們沒有改寫父類別的 __new__ 方法。現(xiàn)在讓我們這樣去處理:
class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) # 復(fù)用type.__new__方法 # 這就是基本的OOP編程,沒什么魔法 return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你可能已經(jīng)注意到了有一個(gè)額外的參數(shù) upperattr_metaclass ,這並沒有什麼特別的。類別方法的第一個(gè)參數(shù)總是表示目前的實(shí)例,就像在普通的類別方法中的 self 參數(shù)一樣。當(dāng)然了,為了清晰起見,這裡的名字我起的比較長(zhǎng)。但是就像 self 一樣,所有的參數(shù)都有它們的傳統(tǒng)名稱。因此,在真實(shí)的產(chǎn)品程式碼中一個(gè)元類別應(yīng)該是像這樣的:
class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith('__') uppercase_attr = dict((name.upper(), value) for name, value in attrs) return type.__new__(cls, name, bases, uppercase_attr)
如果使用super 方法的話,我們還可以使它變得更清晰一些,這會(huì)緩解繼承(是的,你可以擁有元類,從元類繼承,從type 繼承)
class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
通常我們都會(huì)使用元類去做一些晦澀的事情,依賴於自省,控制繼承等等。確實(shí),用元類來搞些「黑暗魔法」是特別有用的,因而會(huì)搞出些複雜的東西來。但就元類別本身而言,它們其實(shí)是很簡(jiǎn)單的:
攔截類別的創(chuàng)建
修改類別
返回修改之後的類別