iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0
Software Development

Python十翼:與未來的自己對話系列 第 16

[Day16] 五翼 - Metaclasses:__call__

  • 分享至 

  • xImage
  •  

今天我們來聊聊__call__。希望透過今天的內容,我們更清楚 my_inst(...)MyClass(...)MyType(...)class MyClass(metaclass=MyType)等語法背後的意義。

我們先記錄以下這段關於type.__call__的論述,稍後再輔以範例說明。

type預設實作有__call__,當其被call時,會呼叫所傳入type instance__new__。如果其返回的是該type instanceinstance時(稱作inst),會再幫忙呼叫type instance__init__,最後回傳insttype instance可能為class或其它繼承typemetaclass或是type本身。

my_inst(...)

當生成instanceclass有實作__call__時,該instancecallable

# 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()`

MyClass(...)

當生成classmetaclass有實作__call__時,該classcallable# 02中,MyClassmetaclass(MyType)有實作__call__,所以MyClasscallable

MyClass被呼叫時,相當於呼叫MyType.__call__,而其將此呼叫delegatesuper()__call__super()__call__相當於super(MyType, cls)__call__,也就是說會將cls(即MyClass)傳入type.__call__MyClasstypeinstance)。結合開頭對type.__call__的描述,我們可以了解type.__call__會幫忙呼叫MyClass.__new__。由於MyClass.__new__回傳了一個MyClassinstance,所以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()`

MyType(...)

當生成metaclassmetaclass有實作__call__時,該metaclass(第一個metaclass)為callable# 03中,MyTypemetaclasstype)有實作__call__,所以MyTypecallable(這就是為什麼我們可以寫出MyType(...)class MyClass(metaclass=MyType)這類語法的原因)。

MyType被呼叫時,相當於呼叫type.__call__,此時MyType會被傳入type.__call__MyTypetypeinstance)。type.__call__會幫忙呼叫MyType.__new__。由於MyType.__new__回傳了一個MyTypeinstance(即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(...)

typemetaclass還是type,而type實作有__call__,所以typecallable

typecall時,相當於呼叫typemetaclass__call__也就是type.__call__,此時type會被傳入type.__call__typetypeinstance)。type.__call__會負責生成一個typeinstance(即class)。

本日筆記

  • 當一個objcallable時,代表建立其的class實作有__call__。換句話說,class實作的__call__是為其instance作準備,而不是自己。當class本身也要是callable時,代表建立其的metaclass也要實作有__call__

  • 如果對上一點很難理解的話,您可以將instanceclassMyType等的生成過程,想成是不同層級的情況,幫助理解。

    • 當一個instancecallable時,代表建立其的class實作有__call__。換句話說,class實作的__call__是為其instance作準備,而不是自己。當class本身也要是callable時,代表建立其的metaclass也要實作有__call__
    • 當一個classcallable時,代表建立其的metaclass實作有__call__。換句話說,metaclass實作的__call__是為其instance(即class)作準備,而不是自己。當metaclass本身也要是callable時,代表建立其的metaclass(或是可以想成meta-metaclass)也要實作有__call__
    • ...
  • 有時候,可能會對__call__到底會產生何種行為,會呼叫誰的__new____init__感到疑惑。此時關鍵是想清楚,究竟是誰在呼叫,是my_inst(...)MyClass(...)MyType(...)type(...)中的哪一層。

Code

本日程式碼傳送門


上一篇
[Day15] 五翼 - Metaclasses:Class Creation
下一篇
[Day17] 五翼 - Metaclasses:Metaclasses相關整理
系列文
Python十翼:與未來的自己對話30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言