1. 程式人生 > >Python學習筆記:單例模式

Python學習筆記:單例模式

turn 實例 提示 type類 metaclass let 所有 python3.6 ips

單例模式:一個類無論實例化多少次,返回的都是同一個實例,例如: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 hello21 {‘val‘: ‘hello‘}22 hi23 {‘val‘: ‘hi‘}24 True
  • 原理:通過給類定義一個類變量,指向本類的一個實例,每次實例化調用__new__的時候都返回這個類變量,可以看到數據結果打印的是True,所以自然就是單例了。
  • 缺點:每次實例化雖然都是同一個實例,但是每次實例化都會調用一次__init__方法,導致這個實例會隨著每次初始化而改變,所以不推薦這種方式來實現單例,因為__new__方法一般是用來改變類結構的。
  • hasattr:類中的私有變量,即加了雙下劃線的變量,在__dict__中會加上一個“_classname”前綴,所以如果這裏使用__instance的話,hasattr(cls, ‘__instance‘)會一直返回False,因為這裏已經不是__instance了,而是_MySingleton__instance。

Python學習筆記:單例模式