今天我們來聊聊__call__。希望透過今天的內容,我們更清楚 my_inst(...)、MyClass(...)及MyType(...)或class MyClass(metaclass=MyType)等語法背後的意義。
我們先記錄以下這段關於type.__call__的論述,稍後再輔以範例說明。
type預設實作有__call__,當其被call時,會呼叫所傳入type instance的__new__。如果其返回的是該type instance的instance時(稱作inst),會再幫忙呼叫type instance的__init__,最後回傳inst。type instance可能為class或其它繼承type的metaclass或是type本身。
當生成instance的class有實作__call__時,該instance為callable。
# 01中,my_inst因為MyClass有實作__call__,所以是callable。
# 01
class MyClass:
def __call__(self):
print('MyClass __call__ called because of `my_inst()`')
if __name__ == '__main__':
my_inst = MyClass()
my_inst() # my_inst is callable
MyClass __call__ called because of `my_inst()`
當生成class的metaclass有實作__call__時,該class為callable。# 02中,MyClass的metaclass(MyType)有實作__call__,所以MyClass為callable。
當MyClass被呼叫時,相當於呼叫MyType.__call__,而其將此呼叫delegate給super()__call__。super()__call__相當於super(MyType, cls)__call__,也就是說會將cls(即MyClass)傳入type.__call__(MyClass是type的instance)。結合開頭對type.__call__的描述,我們可以了解type.__call__會幫忙呼叫MyClass.__new__。由於MyClass.__new__回傳了一個MyClass的instance,所以MyClass.__init__也會被呼叫。
# 02
class MyType(type):
def __call__(cls, *args, **kwargs):
print('MyType __call__ called because of `MyClass()`')
instance = super().__call__(*args, **kwargs)
return instance
class MyClass(metaclass=MyType):
def __new__(cls):
print('MyClass __new__ called')
instance = super().__new__(cls)
return instance
def __init__(self):
print('MyClass __init__ called')
def __call__(self):
print('MyClass __call__ called because of `my_inst()`')
if __name__ == '__main__':
my_inst = MyClass() # MyClass is callable as well
my_inst() # my_inst is callable
MyType __call__ called because of `MyClass()`
MyClass __new__ called
MyClass __init__ called
MyClass __call__ called because of `my_inst()`
當生成metaclass的metaclass有實作__call__時,該metaclass(第一個metaclass)為callable。# 03中,MyType的metaclass(type)有實作__call__,所以MyType為callable(這就是為什麼我們可以寫出MyType(...)或class MyClass(metaclass=MyType)這類語法的原因)。
當MyType被呼叫時,相當於呼叫type.__call__,此時MyType會被傳入type.__call__(MyType是type的instance)。type.__call__會幫忙呼叫MyType.__new__。由於MyType.__new__回傳了一個MyType的instance(即class),所以MyType.__init__也會被呼叫。
此處因為很容易出錯,所以需要再加強說明。MyType.__new__及MyType.__init__之所以被call,並不是因為實作有MyType.__call__,而是因為type實作有__call__。MyType.__call__是為了生成其instance而準備的(例如MyClass),與MyType是否為callable沒有關係。
# 03
class MyType(type):
def __new__(mcls, cls_name, cls_bases, cls_dict, **kwargs):
print('MyType __new__ called')
cls = super().__new__(mcls, cls_name, cls_bases, cls_dict)
return cls
def __init__(cls, cls_name, cls_bases, cls_dict, **kwargs):
print('MyType __init__ called')
def __call__(cls, *args, **kwargs):
print('MyType __call__ called because of `MyClass()`')
instance = super().__call__(*args, **kwargs)
return instance
class MyClass(metaclass=MyType): # MyType is callable as well
def __new__(cls):
print('MyClass __new__ called')
instance = super().__new__(cls)
return instance
def __init__(self):
print('MyClass __init__ called')
def __call__(self):
print('MyClass __call__ called because of `my_inst()`')
if __name__ == '__main__':
my_inst = MyClass() # MyClass is callable as well
my_inst() # my_inst is callable
MyType __new__ called
MyType __init__ called
MyType __call__ called because of `MyClass()`
MyClass __new__ called
MyClass __init__ called
MyClass __call__ called because of `my_inst()`
type的metaclass還是type,而type實作有__call__,所以type為callable。
當type被call時,相當於呼叫type的metaclass的__call__也就是type.__call__,此時type會被傳入type.__call__(type是type的instance)。type.__call__會負責生成一個type的instance(即class)。
當一個obj是callable時,代表建立其的class實作有__call__。換句話說,class實作的__call__是為其instance作準備,而不是自己。當class本身也要是callable時,代表建立其的metaclass也要實作有__call__。
如果對上一點很難理解的話,您可以將instance、class、MyType等的生成過程,想成是不同層級的情況,幫助理解。
instance是callable時,代表建立其的class實作有__call__。換句話說,class實作的__call__是為其instance作準備,而不是自己。當class本身也要是callable時,代表建立其的metaclass也要實作有__call__。class是callable時,代表建立其的metaclass實作有__call__。換句話說,metaclass實作的__call__是為其instance(即class)作準備,而不是自己。當metaclass本身也要是callable時,代表建立其的metaclass(或是可以想成meta-metaclass)也要實作有__call__。有時候,可能會對__call__到底會產生何種行為,會呼叫誰的__new__和__init__感到疑惑。此時關鍵是想清楚,究竟是誰在呼叫,是my_inst(...)、MyClass(...)、MyType(...)或type(...)中的哪一層。