到現(xiàn)在,我們已經知道元類是什么東東了。那么,從始至終我們還不知道元類到底有啥用。只是了解了一下元類。在了解它有啥用的時候,我們先來了解下怎么自定義元類。因為只有了解了怎么自定義才能更好的理解它的作用。
首先我們來了解下 __metaclass__ 屬性
metaclass,直譯為元類,簡單的解釋就是:
當我們定義了類以后,就可以根據這個類創(chuàng)建出實例,所以:先定義類,然后創(chuàng)建實例。
但是如果我們想創(chuàng)建出類呢?那就必須根據metaclass創(chuàng)建出類,所以:先定義metaclass,然后創(chuàng)建類。
連接起來就是:先定義metaclass,就可以創(chuàng)建類,最后創(chuàng)建實例。
所以,metaclass允許你創(chuàng)建類或者修改類。換句話說,你可以把類看成是metaclass創(chuàng)建出來的“實例”。
class MyObject(object):
__metaclass__ = something…
[…]
如果是這樣寫的話,Python 就會用元類來創(chuàng)建類 MyObject。當你寫下 class MyObject(object),但是類對象 MyObject 還沒有在內存中創(chuàng)建。Python 會在類的定義中尋找 __metaclass__ 屬性,如果找到了,Python 就會用它來創(chuàng)建類 MyObject,如果沒有找到,就會用內建的 type 函數(shù)來創(chuàng)建這個類。如果還不怎么理解,看下下面的流程圖:
再舉個實例:
class Foo(Bar):
pass
它的判斷流程是怎樣的呢?
首先判斷 Foo 中是否有 __metaclass__ 這個屬性?如果有,Python 會在內存中通過 __metaclass__ 創(chuàng)建一個名字為 Foo 的類對象(注意,這里是類對象)。如果 Python 沒有找到__metaclass__ ,它會繼續(xù)在 Bar(父類)中尋找__metaclass__ 屬性,并嘗試做和前面同樣的操作。如果 Python在任何父類中都找不到 __metaclass__ ,它就會在模塊層次中去尋找 __metaclass__ ,并嘗試做同樣的操作。如果還是找不到`metaclass` ,Python 就會用內置的 type 來創(chuàng)建這個類對象。
其實 __metaclass__ 就是定義了 class 的行為。類似于 class 定義了 instance 的行為,metaclass 則定義了 class 的行為??梢哉f,class 是 metaclass 的 instance。
現(xiàn)在,我們基本了解了 __metaclass__ 屬性,但是,也沒講過如何使用這個屬性,或者說這個屬性可以放些什么?
答案就是:可以創(chuàng)建一個類的東西。那么什么可以用來創(chuàng)建一個類呢?type,或者任何使用到 type 或者子類化 type 的東東都可以。
元類的主要目的就是為了當創(chuàng)建類時能夠自動地改變類。通常,你會為API 做這樣的事情,你希望可以創(chuàng)建符合當前上下文的類。假想一個很傻的例子,你決定在你的模塊里所有的類的屬性都應該是大寫形式。有好幾種方法可以辦到,但其中一種就是通過在模塊級別設定__metaclass__ 。采用這種方法,這個模塊中的所有類都會通過這個元類來創(chuàng)建,我們只需要告訴元類把所有的屬性都改成大寫形式就萬事大吉了。
幸運的是,__metaclass__ 實際上可以被任意調用,它并不需要是一個正式的類。所以,我們這里就先以一個簡單的函數(shù)作為例子開始。
# 元類會自動將你通常傳給‘type’的參數(shù)作為自己的參數(shù)傳入
def upper_attr(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)
# 通過'type'來做類對象的創(chuàng)建
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr
# 這會作用到這個模塊中的所有類
class Foo(object):
# 我們也可以只在這里定義__metaclass__,這樣就只會作用于這個類中
bar = 'bip'
print hasattr(Foo, 'bar')
# 輸出: False
print hasattr(Foo, 'BAR')
# 輸出:True
f = Foo()
print f.BAR
# 輸出:'bip'
用 class 當做元類的做法:
# 請記住,'type'實際上是一個類,就像'str'和'int'一樣
# 所以,你可以從type繼承
class UpperAttrMetaClass(type):
# __new__ 是在__init__之前被調用的特殊方法
# __new__是用來創(chuàng)建對象并返回之的方法
# 而__init__只是用來將傳入的參數(shù)初始化給對象
# 你很少用到__new__,除非你希望能夠控制對象的創(chuàng)建
# 這里,創(chuàng)建的對象是類,我們希望能夠自定義它,所以我們這里改寫__new__
# 如果你希望的話,你也可以在__init__中做些事情
# 還有一些高級的用法會涉及到改寫__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)
但是,這種方式其實不是 OOP。我們直接調用了 type,而且我們沒有改寫父類的 __new__ 方法?,F(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)
# 復用type.__new__方法
# 這就是基本的OOP編程,沒什么魔法
return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你可能已經注意到了有個額外的參數(shù) upperattr_metaclass ,這并沒有什么特別的。類方法的第一個參數(shù)總是表示當前的實例,就像在普通的類方法中的 self 參數(shù)一樣。當然了,為了清晰起見,這里的名字我起的比較長。但是就像 self 一樣,所有的參數(shù)都有它們的傳統(tǒ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 方法的話,我們還可以使它變得更清晰一些,這會緩解繼承(是的,你可以擁有元類,從元類繼承,從 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)
通常我們都會使用元類去做一些晦澀的事情,依賴于自省,控制繼承等等。確實,用元類來搞些“黑暗魔法”是特別有用的,因而會搞出些復雜的東西來。但就元類本身而言,它們其實是很簡單的: