Python建立單例模式的5種方法

pythontab發表於2017-12-26

單例模式(Singleton Pattern)是一種常用的軟體設計模式,是指一個類的例項從始至終只能被建立一次,同時它提供一個靜態的getInstance()工廠方法,讓客戶可以訪問它的唯一例項;為了防止在外部對其例項化,將其建構函式設計為私有;在單例類內部定義了一個Singleton型別的靜態物件,作為外部共享的唯一例項。 

主要優點:

1、提供了對唯一例項的受控訪問。

2、由於在系統記憶體中只存在一個物件,因此可以節約系統資源,對於一些需要頻繁建立和銷燬的物件單例模式無疑可以提高系統的效能。

3、允許可變數目的例項。

主要缺點:

1、由於單利模式中沒有抽象層,因此單例類的擴充套件有很大的困難。

2、單例類的職責過重,在一定程度上違背了“單一職責原則”。

3、濫用單例將帶來一些負面問題,如為了節省資源將資料庫連線池物件設計為的單例類,可能會導致共享連線池物件的程式過多而出現連線池溢位;如果例項化的物件長時間不被利用,系統會認為是垃圾而被回收,這將導致物件狀態的丟失。

適用場景 

在以下情況下可以考慮使用單例模式: 

- (1) 系統只需要一個例項物件,如系統要求提供一個唯一的序列號生成器或資源管理器,或者需要考慮資源消耗太大而只允許建立一個物件。 

- (2) 客戶呼叫類的單個例項只允許使用一個公共訪問點,除了該公共訪問點,不能透過其他途徑訪問該例項。

實現某個類只有一個例項的途徑:

1,讓一個全域性變數使得一個物件被訪問,但是他不能防止外部例項化多個物件。

2,讓類自身儲存他的唯一例項,這個類可以保證沒有其他例項可以被建立。

多執行緒時的單例模式:加鎖-雙重鎖定

餓漢式單例類:在類被載入時就將自己例項化(靜態初始化)。其優點是躲避了多執行緒訪問的安全性問題,缺點是提前佔用系統資源。

懶漢式單例類:在第一次被引用時,才將自己例項化。避免開始時佔用系統資源,但是有多執行緒訪問安全性問題。

方法1:使用__new__方法

如果想使得某個類從始至終最多隻有一個例項,使用__new__方法會很簡單。Python中類是透過__new__來建立例項的:

class Singleton(object):
  def __new__(cls,*args,**kwargs):
    if not hasattr(cls,'_inst'):
      cls._inst=super(Singleton,cls).__new__(cls,*args,**kwargs)
    return cls._inst
if __name__=='__main__':
  class A(Singleton):
    def __init__(self,s):
      self.s=s   
  a=A('java')  
  b=A('python')
  print id(a),a.s
  print id(b),b.s

結果:

9621235 python
9921235 python

透過__new__方法,將類的例項在建立的時候繫結到類屬性_inst上。如果cls._inst為None,說明類還未例項化,例項化並將例項繫結到cls._inst,以後每次例項化的時候都返回第一次例項化建立的例項。注意從Singleton派生子類的時候,不要過載__new__。

方法2:使用裝飾器

我們知道,裝飾器(decorator)可以動態地修改一個類或函式的功能。這裡,我們也可以使用裝飾器來裝飾某個類,使其只能生成一個例項,程式碼如下:

from functools import wraps
def singleton(cls):
    instances = {}
    @wraps(cls)
    def getinstance(*args, **kw):
        if cls not in instances:
            instances[cls] = cls(*args, **kw)
        return instances[cls]
    return getinstance
@singleton
class MyClass(object):
    a = 1


在上面,我們定義了一個裝飾器 singleton,它返回了一個內部函式 getinstance,該函式會判斷某個類是否在字典 instances 中,如果不存在,則會將 cls 作為 key,cls(*args, **kw) 作為 value 存到 instances 中,否則,直接返回 instances[cls]。

方法3:使用元類(metaclass)

當你編寫一個類的時候,某種機制會使用類名字,基類元組,類字典來建立一個類物件。新型類中這種機制預設為type,而且這種機制是可程式設計的,稱為元類__metaclass__ 。

class Singleton(type):
  def __init__(self,name,bases,class_dict):
    super(Singleton,self).__init__(name,bases,class_dict)
    self._instance=None
  def __call__(self,*args,**kwargs):
    if self._instance is None:
      self._instance=super(Singleton,self).__call__(*args,**kwargs)
    return self._instance
if __name__=='__main__':
  class A(object):
    __metaclass__=Singleton    
  a=A()
  b=A()
  print id(a),id(b)

結果:

43645654 43645654

id是相同的。

例子中我們構造了一個Singleton元類,並使用__call__方法使其能夠模擬函式的行為。構造類A時,將其元類設為Singleton,那麼建立類物件A時,行為發生如下:

A=Singleton(name,bases,class_dict),A其實為Singleton類的一個例項。

建立A的例項時,A()=Singleton(name,bases,class_dict)()=Singleton(name,bases,class_dict).__call__(),這樣就將A的所有例項都指向了A的屬性_instance上,這種方法與方法1其實是相同的。

方法4:使用模組

python中的模組module在程式中只被載入一次,本身就是單例的。可以直接寫一個模組,將你需要的方法和屬性,寫在模組中當做函式和模組作用域的全域性變數即可,根本不需要寫類。

而且還有一些綜合模組和類的優點的方法:

class _singleton(object):
  class ConstError(TypeError):
    pass
  def __setattr__(self,name,value):
    if name in self.__dict__:
      raise self.ConstError
    self.__dict__[name]=value
  def __delattr__(self,name):
    if name in self.__dict__:
      raise self.ConstError
    raise NameError
import sys
sys.modules[__name__]=_singleton()

python並不會對sys.modules進行檢查以確保他們是模組物件,我們利用這一點將模組繫結向一個類物件,而且以後都會繫結向同一個物件了。

將程式碼存放在single.py中:

>>> import single
>>> single.a=1
>>> single.a=2
ConstError
>>> del single.a
ConstError

方法5:名字繫結法

最簡單的方法:

class singleton(object):
  pass
singleton=singleton()

將名字singleton繫結到例項上,singleton就是它自己類的唯一物件了。


相關文章