元類
前言
本篇部落格學習 python 中一種高階概念,元類。在《說文》中這樣描述 元 :元,始也(ps:這也太簡短了)。三個字,最後一個字還是語氣詞。。很形象的說明了元的意思,最開始的意思,在道教中元的意思是:開始的,最初。第一。為首的。主要,根本,整體的意思。那麼很顯然,元類是最開始的類的意思。我們都知道在 python 中一切皆為物件,而物件都繼承與 object 類,那麼元類與 object 的關係是什麼呢?
可以看出元類也是繼承自 object 類的。
那麼元類和類的關係是什麼呢?
簡單來講就是:類生成物件,元類生成自己本身同時例項化其他所有的類。
元類
類作為物件
之前已經說過 python 中一切皆物件,那麼類同樣也是物件,而且類是元類的物件。當使用關鍵詞 class 定義一個類時,在程式碼執行階段就會建立一個 空的object,並使用元類的__init__方法來出初始化一個類。這個物件(類)本身可以建立物件,因為它是一個類。
# 在記憶體中建立一個 Foo 物件 class ObjectCreator(object): pass
但是同樣的它也是一個物件,一個元類的例項物件。
所以從物件層面將和它自己例項出來的物件沒有什麼不同,因此:
- 可以將類賦值給一個變數
- 可以複製它
- 可以為類新增其他的屬性
- 可以將其作為引數來傳遞
例如:
>>> print(ObjectCreator) # you can print a class because it's an object <class '__main__.ObjectCreator'> >>> def echo(o): ...print(o) ... >>> echo(ObjectCreator) # you can pass a class as a parameter <class '__main__.ObjectCreator'> >>> print(hasattr(ObjectCreator, 'new_attribute')) False >>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class >>> print(hasattr(ObjectCreator, 'new_attribute')) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c>
動態建立類
由於類也是物件,因此可以像任何物件一樣動態建立它們。 在python中,type 除了可以檢視物件的型別,還有一個很強大的功能,type 可以動態的建立類。type 可以將類的描述作為引數並返回一個類。
使用 class 關鍵詞時,python 會自動的建立此物件,就像例項化一個物件時,呼叫的是類中的__init__方法,建立物件時同樣的呼叫了元類中的__init__方法:
該函式有四個引數,第一個引數為 cls 表示這是一個由類呼叫的初始化函式,python 在檢測語法的時候發現 class 則會把 class 後面的類名當做引數傳給 what ,bases 表示繼承的父類是誰,是個元祖型別(因為會有多繼承), dict 是個字典型別,是類的名稱空間,可以通過 __dict__ 檢視,使用 type來建立一個類:
what = 'Music' bases = (object,) dict = {'music_name': '南山憶'}
這和我們使用 class 關鍵詞定義一個類沒有什麼不同:
之前在學習類的時候,我們知道建立一個物件是呼叫了類中__init__方法,同理在建立類的時候是呼叫了元類中也就是 type 中的__init__方法,所以我們可以通過改寫 type 中的元類來自定義建立類,比如加些判斷或者其他的屬性:
可惜想象是美好的,這招根本行不通,那麼有沒有別的辦法呢?當然有啦,哈哈哈哈哈
在學習類的三大特性的時候,從父類繼承的屬性可以改寫(多型的思想),那麼改寫不了元類是因為元類很特殊,那我繼承自元類的類肯定可以改寫吧。這個到後面再講,突然發現什麼是元類還沒講清楚。。。
什麼是元類
我把元類稱之為 類的類 。元類是建立類的‘’東西‘’,我們定義類來建立物件,類也是物件,所以定義了一個元類用來建立物件。 type 是 Python 用來建立所有類的元類(不包括 object 類)。其實這和用 str 建立字串物件, int 建立整數物件一致的。 type 只是建立類物件的類。
一切,all thing都是 Python 中的一個物件。這包括整數、字串、函式和類。所有這些都是物件,所有這些都是從一個類建立的。因此,元類是建立類物件的東西。
__metaclass__屬性
除了使用 type 動態建立類以外,要控制類的建立行為,可以使用 metaclass,這也是自定義元類的方法。
metaclass 的意思就是元類:當我們定義了類以後,就可以根據這個類創建出例項,所以先定義類,然後建立例項。但是如果想創建出類呢?那就必須根據 metaclass 創建出類,所以:先定義元類(不自定義時,預設用 type),然後建立類。(大部分情況使用不到 metaclass,除非想自定義元類)。
預設情況下,類是使用 type 構造的。類主體在一個新的名稱空間中執行,類名在本地繫結到型別的結果(名稱,基,名稱空間)。
可以通過類定義行中傳遞元類關鍵字引數來定製類的建立過程,或者從包含此類引數的現有類繼承。
例項化物件的完整過程
class Foo(Bar): pass
當直譯器執行這行程式碼時,執行以下操作:
Foo 有__metaclass__屬性嗎?如果有的話,python 會通過 __metaclass__在記憶體中建立一個名稱為 Foo 的類物件。如果 Python 沒有找到__metaclass__,它會繼續在 Bar 中尋找__metaclass__屬性,並嘗試做和前面同樣的操作。如果 Python 在任何父類中都找不到__metaclass__,它就會在模組層次中去尋找__metaclass__,並嘗試做同樣的操作。如果還找不到__metaclass__,python 就會用內建的 type 來建立這個類物件。
那麼在自定義類是,可以在__metaclass__中放置什麼程式碼呢?
可以放用來建立一個類的東西,type 或者 type的子類都可以放。
以上面的程式碼為例,我們例項化一個物件obj=Foo(),會先執行 Foo 類中的__new__方法,沒有則使用父類的__new__方法,建立一個空物件並返回,然後執行__init__方法(自己有就用自己的,沒有就用父類的,這裡分兩種情況,如果是建立一個類的物件,那就是使用父類的,因為自己沒有;如果是建立類,自己有就是用自己的,否則就是用父類的),為建立的物件進行初始化。
obj()會執行 Foo 類的__call__方法,沒有則用父類的。現在已經知道。類同樣也是物件,是元類的物件,即例項化一個物件(類)時,呼叫其父類(元類)的__call__方法。
元類處理過程:定義一個類時,使用宣告或者預設的元類對該類進行建立,對元類求 type 運算,得到父元類(該類宣告元類的父元類),呼叫父元類的__call__方法,在父元類的__call__方法中,呼叫該類宣告的元類的__new__來建立一個空物件(該方法需要返回一個類物件例項),然後再呼叫該元類的__init__方法初始化該類物件,最終返回一個類。
- 物件時類建立,建立物件時類的__init__方法自動執行,物件()則會執行類的__call__方法;
- 類是由 type 建立的,class 定義的時候 type 的__init__方法自動執行,類()則會執行type 的__call__方法(類的__new__,類的__init__方法)
元類的__new__方法和__init__影響的是建立類的物件行為(不是建立類),父元類的__call__控制對子元類的__new__,__init__的呼叫,就是說控制類物件的建立和初始化。父元類的__new__和__init__由更上層的元類控制,一般來說,原始 type 是最初的父元類,其__new__和__init__是最具有普遍意義的,即應該是分配記憶體、初始化相關資訊等。元類的__call__方法影響的是建立類的例項物件的行為,所以這時候自定義__call__就可以控制建立類物件的例項物件的行為了。比如單例模式的建立。
__new__和__init__影響的是建立物件的行為,當這些函式在元類中時,影響建立的是類;同理,當這兩個函式在普通類中時,影響的是建立普通的物件例項行為。
__call__影響()呼叫行為,__call__是在建立類的時候呼叫,即:定義類時就是建立類,此時會呼叫元類的__call__,如果元類有繼承,子元類定義時執行的是父元類的__call__。如果是普通類例項化,呼叫的是普通類的__call__。(昨晚上卡在這裡了,其實例項化一個普通的物件時,都是呼叫其父類的__call__方法,除了元類,普通類中不會有__call__方法。)
自定義元類
元類的主要目的是在建立類時自動更改類,比如想要將建立的所有類都變成首字母大寫的:
class MymetaClass(type): def __call__(cls, *args, **kwargs): if type(args[0]) != str: raise TypeError('引數必須為字串型別') obj = object.__new__(cls) obj.__init__(*args, **kwargs) return obj class Foo(metaclass=MymetaClass): def __init__(self, name): self.name = name res = Foo(123)
這就是自定義元類的好處,可以在__call__來對傳入的引數進行一些判斷來做一些自定義操作。
通過函式
# the metaclass will automatically get passed the same argument # that you usually pass to `type` def upper_attr(future_class_name, future_class_parents, future_class_attr): """ Return a class object, with the list of its attribute turned into uppercase. """ # pick up any attribute that doesn't start with '__' and uppercase it uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # let `type` do the class creation return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # this will affect all classes in the module class Foo(): # global __metaclass__ won't work with "object" though # but we can define __metaclass__ here instead to affect only this class # and this will work with "object" children bar = 'bip' print(hasattr(Foo, 'bar')) # Out: False print(hasattr(Foo, 'BAR')) # Out: True f = Foo() print(f.BAR) # Out: 'bip'
通過元類
# remember that `type` is actually a class like `str` and `int` # so you can inherit from it class UpperAttrMetaclass(type): # __new__ is the method called before __init__ # it's the method that creates the object and returns it # while __init__ just initializes the object passed as parameter # you rarely use __new__, except when you want to control how the object # is created. # here the created object is the class, and we want to customize it # so we override __new__ # you can do some stuff in __init__ too if you wish # some advanced use involves overriding __call__ as well, but we won't # see this def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type(future_class_name, future_class_parents, uppercase_attr)
使用元類的__new__方法
因為 type 的__new__不會被覆蓋,所以可以使用:
class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # reuse the type.__new__ method # this is basic OOP, nothing magic in there return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
使用 super 的__new__方法
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, dct): uppercase_attr = {} for name, val in dct.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
使用元類的程式碼複雜性背後的原因不是因為元類,而是因為你通常使用元類來依賴於內省,操縱繼承,變數__dict__等等來做扭曲的東西。實際上,元類特別適用於製作黑魔法,因此也很複雜。但他們本身很簡單:
- 攔截類的建立
- 自定義類
- 返回修改後的類
為什麼要使用元類而不是函式?
既然__metaclass__可以接收任何呼叫,那麼為什麼要使用一個類,因為類顯然比函式要複雜。
有幾個原因:
- 目的很明確,你知道使用元類會發生什麼;
- 可以使用 OOP。metaclass 可以從元類繼承,覆蓋父方法,元類甚至可以使用元類;
- 如果你指定了元類,但沒有使用元類函式,則類的子類將其元類的例項;
- 可以更好地構建程式碼;
- 可以和__new__,__init__,__call__搭配使用,來使得建立一個類或者一個類例項變得更有創造性。
本文參考與一下兩篇文章
ofollow,noindex" target="_blank">https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python
https://www.cnblogs.com/huchong/p/8260151.html
感謝