簡單一句話,當一個類實現__call__方法時,這個類的實例就會變成可調用對象。
直接上測試代碼
class ClassA: def __call__(self, *args, **kwargs): print('call ClassA instance') if __name__ == '__main__': # ClassA實現了__call__方法 a = ClassA() ''' 這個時候,ClassA的實例a,就變成可調用對象 調用a(),輸出call ClassA instance,說明是調用了 __call__函數 ''' a() # 其實a()等同於a.__call__(),它本質上就是后者的縮寫 a.__call__() # 判斷是否可調用,輸出True print(callable(a))
注意,是這個類的實例變成可調用對象,類的實例變成可調用對象,類的實例變成可調用對象,而不是改變這個類的實例化行為。
那么,如果要改變一個類的被實例化行為呢?
當然要用上黑魔法元類了,因為類本身就是元類的實例,當我們在元類中定義__call__的函數時,會改變類的實例化行為(或者說被調用的行為?感覺類和函數的界限有些模糊了)。
利用元類和__call__,可以在不使用工廠函數的情況,輕松實現單例模式,同時保持不錯的可讀性。
(以下代碼來自《Python Cookbook》,進行部分修改,同時注釋部分為個人理解)。
完整源碼:https://github.com/blackmatrix7/python-learning/blob/master/class_/singleton.py
先定義一個名為Singleton的元類,實現如下
class Singleton(type): def __init__(cls, *args, **kwargs): cls.__instance = None super().__init__(*args, **kwargs) # __call__ 是對於類實例有效,比如說Spam類,是type類的實例 def __call__(cls, *args, **kwargs): print('Singleton __call__ running') if cls.__instance is None: ''' 元類定義__call__方法,可以搶在類運行 __new__ 和 __init__ 之前執行, 也就是創建單例模式的前提,在類實例化前攔截掉。 type的__call__實際上是調用了type的__new__和__init__ ''' cls.__instance = super().__call__(*args, **kwargs) return cls.__instance else: return cls.__instance
使用元類創建類Spam
class Spam(metaclass=Singleton): def __new__(cls): print('Spam __new__ running') return super().__new__(cls) def __init__(self): print('Spam __init__ running')
解釋下上面的代碼,在元類Singleton的__init__函數中,給類增加了一個叫__instance的類屬性。
需要注意的是,__init__的第一個參數是cls,其實等同於我們平時在類中定義__init__的self,因為對於元類來說,類是它的實例。
之所以寫成cls,是便於理解 cls.__instance = None 是給類屬性 __instance 賦值為 None。
接着在元類Singleton中重寫__call__方法,__call__會搶在類(元類的實例)執行__new__和__init__之前執行,也就為攔截類的實例化提供了可能。
在元類Singleton的__call__方法對類屬性__instance進行判斷,如果__instance為None,說明類還未進行實例化,那么調用元類的父類(元類是type的子類)type的__call__方法,同時賦值給 cls.__instance。如果 cls.__instance 不為None,說明類已經進行過實例化,直接返回之前存儲在類屬性cls.__instance 中的類實例,即實現單例模式。
測試代碼:
if __name__ == '__main__': a = Spam() b = Spam() print(a is b) c = Spam() print(a is c)
執行結果:
Singleton __call__ running Spam __new__ running Spam __init__ running Singleton __call__ running True Singleton __call__ running True
從運行結果上可以看出,每次嘗試實例化Spam時,會被__call__函數攔截,所以會打印出:Singleton __call__ running
接着判斷實例是否存在,在第一次運行時,實例不存在,創建類實例,並賦值給類屬性__instance。
后續的幾次嘗試實例化Spam,因為Spam已經有實例存在,不在創建實例,實現了單例模式。
一個錯誤的例子
如果,我們把Spam的__new__改成下面這樣,不返回任何結果,會有什么問題??
class Spam(metaclass=Singleton): def __new__(cls): print('Spam __new__ running') def __init__(self): print('Spam __init__ running')
還是執行之前的測試代碼,得到下面的運行結果
Singleton __call__ running Spam __new__ running Singleton __call__ running Spam __new__ running True Singleton __call__ running Spam __new__ running True
初看似乎沒有什么問題,print(a is b) 和 print(a is c)都返回了True。
細心的話,會發現上面的輸出結果有個很奇怪的問題,__init__方法是從未被執行!!!
對於此,官方文檔是如此說明的
If
__new__()
does not return an instance of cls, then the new instance’s__init__()
method will not be invoked.
當__new__函數沒有返回這個類的實例時,__init__函數不會被調用。上面的示例函數,只是打印了文字,沒有返回任何結果,所以不會執行__init__。
其實也容易理解,__init__需要類實例self參數,而__new__沒有返回一個類實例,這樣的話__init__自然無法運行。
但是,還有個問題,為什么每次Spam的__new__方法都會被運行??
因為 __new__ 不返回任何結果,那么__init__方法不會執行,實例從頭到尾都沒有被創建過(cls.__instance永遠是None)。
在 執行 a = Spam() b = Spam() c = Spam()的過程中,每次cls.__instance為None,就調用type.__call__試圖創建實例。
而每次執行到Spam的__new__方法時,會沒有創建出任何實例,這樣每次都不能成功創建實例。
所以,會有每次都執行Spam __new__ running的情況。
最后,因為 a、b、c 三個實例都是None,所以在做比較時,永遠的True。