今天我們來歸納整理一下metaclass
相關的知識。
# 01
為稍後會用到的程式碼,其內含有:
MyClass
的class
。MyClass
生成,名為my_inst
的instance
。# 01
class MyClass(object, metaclass=type):
pass
my_inst = MyClass()
以下是我們能想到最精簡能解釋object
、type
、class
、instance
關係的解釋,或許您也會想參考這篇stackoverflow的討論。
在Python,萬物皆是object
,所以萬物都是繼承object
而來(包含object
自己)。所以
>>> isinstance(object, object) # True
>>> issubclass(object, object) # True
我們說my_inst
是MyClass
的instance
。所以type(my_inst)
是MyClass
。
同理,藉由觀察type(MyClass)
為type
,可以得知MyClass
是type
的instance
(MyClass(metaclass=type)
是強烈暗示)。
那麼再往上推,藉由觀察type(type)
為type
,可以得知type
也是type
的instance
。其實type is type
也會是True
。這就像一個circular reference
。所以
>>> isinstance(type, type) # True
>>> issubclass(type, type) # True
而MyClass
是繼承object
而來(MyClass(object)
是強烈暗示),且MyClass
既然是type
的instance
,那麼object
必定也是type
的instance
。所以
>>> isinstance(object, type) # True
再加上type
的circular reference
,所以
>>> issubclass(object, type) # True
type
也是繼承object
而來,再加上type
的circular reference
,所以
>>> isinstance(type, object) # True
>>> issubclass(type, object) # True
class
都是繼承object
而來,且預設metaclass
為type
。class
關鍵字後,知道我們想生成一個class
時,會先呼叫type.__prepare__
,準備一個mapping
,作為稍後傳給type.__new__
的cls_dict
,裡面會幫我們加上一些attribute
(如__qualname__
)。class
,相當於我們要呼叫type
,type
本身是callable
,因為其metaclass
(還是type
)有實作__call__
。type.__call__
會先呼叫type.__new__
,如果回傳的是MyClass
的話,則會再呼叫type.__init__
。至此MyClass
生成完畢。instance
,相當於我們要呼叫MyClass
,這會呼叫MyClass
的metaclass
的__call__
,即type.__call__
。type.__call__
會呼叫MyClass.__new__
,如果其回傳的是MyClass
的instance
,則會再呼叫MyClass.__init__
。# 02
中,我們講解了class
與instance
的生成過程,並提供數個可以生成class variable
及instance variable
的方法。
# 02
class MyType(type):
mcls_var_x = 'x'
def __prepare__(cls, cls_bases, **kwargs):
cls_dict = {'cls_var_a': 'a'}
return cls_dict
def __new__(mcls, cls_name, cls_bases, cls_dict, **kwargs):
cls_dict['cls_var_c'] = 'c'
cls = super().__new__(mcls, cls_name, cls_bases, cls_dict)
cls.say_hello = lambda self: 'MyType works!'
return cls
def __init__(cls, cls_name, cls_bases, cls_dict, **kwargs):
cls.cls_var_d = 'd'
def __call__(cls, *args, **kwargs):
instance = super().__call__(*args, **kwargs)
instance.inst_var_c = 'c'
return instance
class MyParentClass:
cls_var_e = 'e'
def good_morning(self):
return 'Good morning!'
class MyClass(MyParentClass, metaclass=MyType):
cls_var_b = 'b'
def __new__(cls, b):
instance = super().__new__(cls)
instance.inst_var_a = 'a'
return instance
def __init__(self, b):
self.inst_var_b = b
if __name__ == '__main__':
my_inst = MyClass('b')
cls_vars = {k: v
for k, v in vars(MyClass).items()
if k.startswith('cls_')}
# {'cls_var_a': 'a', 'cls_var_b': 'b', 'cls_var_c': 'c', 'cls_var_d': 'd'}
print(cls_vars)
inst_vars = vars(my_inst)
# {'inst_var_a': 'a', 'inst_var_b': 'b', 'inst_var_c': 'c'}
print(inst_vars)
# MyType.__new__
print(my_inst.say_hello()) # MyType works!
# MyParentClass
print(my_inst.cls_var_e, MyClass.cls_var_e) # e e
print(my_inst.good_morning()) # Good morning!
# MyType
print(MyClass.mcls_var_x) # x
# print(my_inst.mcls_var_x) # AttributeError
# (<class '__main__.MyType'>, <class 'type'>, <class 'object'>)
print(type(MyClass).__mro__)
# (<class '__main__.MyClass'>, <class '__main__.MyParentClass'>, <class 'object'>)
print(MyClass.__mro__)
MyClass
由MyType
所生成。
MyType.__prepare__
會先生成一個mapping
,我們此處直接生成一個dict
,並於其中建立cls_var_a
。MyType
會呼叫type.__call__
(不是MyType.__call__
),幫忙呼叫MyType.__new__
(不是type.__new__
,因為MyType
呼叫type.__call__
時,有將MyType
的資訊傳給type.__call__
)。MyType.__new__
中,我們利用super().__new__
(即type.__new__
)來生成MyClass
。此時的cls_dict
中除了cls_var_a
還有位於MyClass
中的cls_var_b
。我們可以選擇將想建立的attribute
或function
放入cls_dict
中,或是於生成MyClass
後再動態指定。此處我們演示將cls_var_c
放入cls_dict
並於生成MyClass
後,再動態指定say_hello
function
。MyType.__new__
中返回的是MyClass
,所以type.__call__
會幫忙呼叫MyType.__init__
。於MyType.__init__
中我們加入cls_var_d
。my_inst
需要呼叫MyClass
,所以會呼叫MyType.__call__
。MyType.__call__
將呼叫的工作delegate給super().__call__
,所以實際上幫忙我們建立instance
的是type.__call__
。但此處我們將MyClass
的資訊藉由super().__call__
傳給type.__call__
,所以Python會呼叫MyClass.__new__
(非type.__new__
)來生成instance
。MyClass.__new__
中我們加入inst_var_a
。由於回傳的是MyClass
的instance
,所以MyType.__call__
會幫忙呼叫MyClass.__init__
。於MyClass.__init__
中我們加入inst_var_b
。MyType.__call__
回傳instance
前加入inst_var_c
。上面我們示範了cls_var_a
、cls_var_b
、cls_var_c
、cls_var_d
、inst_var_a
、inst_var_b
、inst_var_c
以及say_hello
function
是如何生成的。
接下來討論兩個有趣的情況。
因為MyClass
繼承MyParentClass
,所以可以存取位於MyParentClass
的cls_var_e
及good_morning
function
。
如果使用MyClass.mcls_var_x
可以取回'x'
,但若使用my_inst.mcls_var_x
會raise AttributeError
。
不知道諸位對這個行為是否會感到疑惑呢?如果要了解Python整個attribute lookup
,可能要閱讀六翼的文章。
簡單解釋的話,是當於instance.__dict__
內找不到某attribute
時,會往生成其的class
及其MRO
的__dict__
中尋找。
type(MyClass).__mro__
為(MyType, type, object)
由於MyClass
是MyType
的instance
,當於MyClass.__dict__
找不到mcls_var_x
時,會往MyType.__dict__
尋找。由於mcls_var_x
是MyType
的class variable
,所以返回其值'x'
。
Myclass.__mro__
為(MyClass, MyParentClass, object)
至於my_inst
是MyClass
的instance
,當於myinst.__dict__
找不到mcls_var_x
時,會往MyClass.__dict__
尋找。由於找不到,於是再往上至MyParentClass.__dict__
尋找。由於還是找不到,於是再往上至object.__dict__
尋找。最後由於整個MRO
中都找不到mcls_var_x
,只能raise AttributeError
給使用者。
__init_subclass__
__init_subclass__
是於Python3.6所添加的,其會於type.__new__
生成class
後才被呼叫。type.__new__
會先收集有實作__set_name__
的attribute
,於class
生成後呼叫這些attribute
的__set_name__
。接下來__init_subclass__
會由MRO
上最接近的parent class來呼叫。
__init_subclass__
是一個class method
,它給我們一個使用繼承來mutate
class
的選項。我們可以使用其添加class
的attribute
或function
等等,一些以前必須要在type.__new__
中的邏輯,可以轉移到這邊,而不需要自己客製metaclass
。
最後,每當迷失在metaclass
的世界時,下面這段話總是能幫到我們,希望對您也有幫助。
instance
由class
所生成,所以instance
是class
生成的instance
。class
由metaclass
所生成,所以class
是metaclass
生成的instance
。
將class
視為一種instance
,再回頭看metaclass
時,會有一種撥雲見日的感覺。
__call__
。