Python學習筆記:單例模式

  • 2020 年 1 月 19 日
  • 筆記

單例模式:一個類無論實例化多少次,返回的都是同一個實例,例如:a1=A(), a2=A(), a3=A(),a1、a2和a3其實都是同一個對象,即print(a1 is a2)和print(a2 is a3)都會列印True。

實現方式:有兩種方式,一種是使用元類metaclass控制類實例化時的對象,另一種是使用類的__new__方法控制類返回的對象,推薦使用元類的方式,因為__new__通常是用來改變類結構的。

註:關於元類和單例模式,本文只是貼了兩個簡單的示例程式碼和自己的一些心得,想要更加深入的學習,這裡有一篇部落格講得很詳細https://www.cnblogs.com/tkqasn/p/6524879.html

元類實現單例模式(Python3.6):

 1 class Singleton(type):   2     def __init__(cls, *args, **kwargs):   3         cls.__instance = None   4         super().__init__(*args, **kwargs)   5   6     def __call__(cls, *args, **kwargs):   7         if cls.__instance is None:   8             cls.__instance = super().__call__(*args, **kwargs)   9  10         return cls.__instance  11  12  13 class MySingleton(metaclass=Singleton):  14     def __init__(self, val):  15         self.val = val  16         print(self.val)  17  18  19 hello = MySingleton('hello')  20 hi = MySingleton('hi')  21 print(hello is hi)  22  23 ----------輸出結果----------  24 hello  25 True
  • metaclass:Python3中metaclass是通過指定metaclass實現的,Python2中是通過指定類變數__metaclass__來實現的,但原理都是一樣的。
  • type:Python中所有的類都是type類的實例,即一個類(還未實例化)的定義,其實就是type類(Python內建元類)的實例。如下的列印可以更加直觀的理解這一點: >>> int.__class__ <class 'type'> >>> num = 3 >>> num.__class__ <class 'int'> >>> num.__class__.__class__ <class 'type'> >>> >>> >>> class A: pass >>> A.__class__ <class 'type'> >>> a = A() >>> a.__class__ <class '__main__.A'> >>> a.__class__.__class__ <class 'type'> >>>
  • __call__:當調用一個實例時,即執行實例加括弧的形式,就會調用該實例的__call__方法,如果沒有定義(需要自己定義),則會報錯。例如a=A(),a()就會調用a的__call__方法。
  • 程式碼執行流程:第一步執行MySingleton時(即沒加括弧的部分),進行元類的實例化,即MySingleton=Singleton(),Singleton的實例化和普通類一樣會先執行__new__返回該類的實例,然後自動執行該實例的__init__方法進行初始化,此示例中的初始化方法給該實例賦予了一個值為None的__instance變數;第二步執行MySingleton('hello')時,進行類的實例化,即MySingleton('hello')=Singleton()('hello'),這裡就會調用到Singleton的__call__方法了,而super().__call__即調用type的__call__方法,這時候就和普通類實例化一樣會調用MySingleton的__new__和__init__方法了。
  • 原理:由於每次實例化MySingleton時都會先調用metaclass中的__call__方法,所以只有第一次實例化時才會執行MySingleton的__new__和__init__,後面的實例化都只會返回第一次實例化好的實例,所以導致的結果就是無論進行多少次實例化,都給你返回同一個實例,當然就只有單例了(所以「輸出結果」中就沒有列印「hi」了) 。
  • cls和self:Singleton的編寫,在eclipse中提示需要寫成self,在PyCharm中提示需要寫成cls,因為參數self是約定代表實例本身,但在這裡type的實例就是類,所以推薦寫成cls。

__new__實現單例模式(Python3.6):

 1 class MySingleton:   2     def __init__(self, val):   3         self.val = val   4         print(self.val)   5         print(self.__dict__)   6   7     def __new__(cls, *args, **kwargs):   8         if not hasattr(cls, '_instance'):   9             cls._instance = super().__new__(cls)  10  11         return cls._instance  12  13  14 hello = MySingleton('hello')  15 hi = MySingleton('hi')  16 print(hello is hi)  17  18  19 -----------輸出結果--------------  20 hello  21 {'val': 'hello'}  22 hi  23 {'val': 'hi'}  24 True
  • 原理:通過給類定義一個類變數,指向本類的一個實例,每次實例化調用__new__的時候都返回這個類變數,可以看到數據結果列印的是True,所以自然就是單例了。
  • 缺點:每次實例化雖然都是同一個實例,但是每次實例化都會調用一次__init__方法,導致這個實例會隨著每次初始化而改變,所以不推薦這種方式來實現單例,因為__new__方法一般是用來改變類結構的。
  • hasattr:類中的私有變數,即加了雙下劃線的變數,在__dict__中會加上一個「_classname」前綴,所以如果這裡使用__instance的話,hasattr(cls, '__instance')會一直返回False,因為這裡已經不是__instance了,而是_MySingleton__instance。