1. 程式人生 > 實用技巧 >【python設計模式-建立型】單例模式

【python設計模式-建立型】單例模式

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。

這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。

注意:

  • 1、單例類只能有一個例項。
  • 2、單例類必須自己建立自己的唯一例項。
  • 3、單例類必須給所有其他物件提供這一例項。

意圖:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

主要解決:一個全域性使用的類頻繁地建立與銷燬。

何時使用:當您想控制例項數目,節省系統資源的時候。

如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則建立。

關鍵程式碼:建構函式是私有的。

應用例項:

  • 1、一個班級只有一個班主任。
  • 2、Windows 是多程序多執行緒的,在操作一個檔案的時候,就不可避免地出現多個程序或執行緒同時操作一個檔案的現象,所以所有檔案的處理必須通過唯一的例項來進行。
  • 3、一些裝置管理器常常設計為單例模式,比如一個電腦有兩臺印表機,在輸出的時候就要處理不能兩臺印表機列印同一個檔案。

優點:

  • 1、在記憶體裡只有一個例項,減少了記憶體的開銷,尤其是頻繁的建立和銷燬例項(比如管理學院首頁頁面快取)。
  • 2、避免對資源的多重佔用(比如寫檔案操作)。

缺點:

沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來例項化。

使用場景:

  • 1、要求生產唯一序列號。
  • 2、WEB 中的計數器,不用每次重新整理都在資料庫里加一次,用單例先快取起來。
  • 3、建立的一個物件需要消耗的資源過多,比如 I/O 與資料庫的連線等。

注意事項:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多執行緒同時進入造成 instance 被多次例項化。

實現

1、第一種方式

自己定義一個包,例如mySingleton.py

class Singleton(object):
    def foo(self):
        pass
singleton 
= Singleton()

然後在其它的python檔案中就可以使用:from mySingleton import singleton

2、第二種方式:使用裝飾器

def Singleton(cls):
    _instance={}
    def _singleton(*args,**kargs):
        if cls not in _instance:
            _instance[cls]=cls(*args,**kargs)
        return _instance[cls]
    return _singleton
@Singleton
class A(object):
    a = 1
    def __init__(self, x=0):
        self.x = x

a1 = A(2)
a2 = A(3)
print(a1.a)
print(a2.a)
print(a1.x)
print(a2.x)
print(id(a1))
print(id(a2))

執行結果:

值得關注的幾個點:

(1) a1和a2的記憶體地址是一致的,說明是同一個物件;

(2)單例模式只例項化一次類,因此a1.x和a2.x的值都是a1初始化之後的值,也就是2。

3、使用類

class Singleton(object):

    def __init__(self,x):
        self.x=x

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance


a1 = Singleton(4)
a2 = Singleton(5) 
print(id(a1))
print(id(a2))
print(a1.x)
print(a2.x)
print(id(a1.instance(2)))
print(id(a2.instance(3)))
print(a1.x)
print(a2.x)

輸出:

需要關注的幾個點:

(1)Singleton的例項a1和a2不是同一個物件,但是a1.instance()和a2.instance()確是同一個物件。

(2)x初始化之後的值沒有被instance(x)所改變。

也就是這裡的單例實際上是Singleton.instance()

但是上述這種實現會存線上程不安全,例如以下程式碼:

class Singleton(object):

    def __init__(self):
        pass

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

import threading

def task(arg):
    obj = Singleton.instance()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()

看起來也沒有問題,那是因為執行速度過快,如果在init方法中有一些IO操作,就會發現問題了,下面我們通過time.sleep模擬

我們在上面__init__方法中加入以下程式碼:

   def __init__(self):
        import time
        time.sleep(1)

問題出現了!按照以上方式建立的單例,無法支援多執行緒

解決辦法:加鎖!未加鎖部分併發執行,加鎖部分序列執行,速度降低,但是保證了資料安全

import time
import threading
class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        time.sleep(1)

    @classmethod
    def instance(cls, *args, **kwargs):
        with Singleton._instance_lock:
            if not hasattr(Singleton, "_instance"):
                Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance


def task(arg):
    obj = Singleton.instance()
    print(obj)
for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)

這樣就差不多了,但是還是有一點小問題,就是當程式執行時,執行了time.sleep(20)後,下面例項化物件時,此時已經是單例模式了,但我們還是加了鎖,這樣不太好,再進行一些優化,把intance方法,改成下面的這樣就行:

@classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

這樣,一個可以支援多執行緒的單例模式就完成了。

4、基於__new__方法實現(推薦使用,方便)

通過上面例子,我們可以知道,當我們實現單例時,為了保證執行緒安全需要在內部加入鎖

我們知道,當我們例項化一個物件時,是先執行了類的__new__方法(我們沒寫時,預設呼叫object.__new__),例項化物件;然後再執行類的__init__方法,對這個物件進行初始化,所有我們可以基於這個,實現單例模式

import threading
class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        pass


    def __new__(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    Singleton._instance = object.__new__(cls)  
        return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)

def task(arg):
    obj = Singleton()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()

採用這種方式的單例模式,以後例項化物件時,和平時例項化物件的方法一樣obj = Singleton()

5、基於metaclass實現

"""
1.類由type建立,建立類時,type的__init__方法自動執行,類() 執行type的 __call__方法(類的__new__方法,類的__init__方法)
2.物件由類建立,建立物件時,類的__init__方法自動執行,物件()執行類的 __call__ 方法
"""
class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        pass

obj = Foo()
# 執行type的 __call__ 方法,呼叫 Foo類(是type的物件)的 __new__方法,用於建立物件,然後呼叫 Foo類(是type的物件)的 __init__方法,用於對物件初始化。

obj()    # 執行Foo的 __call__ 方法

元類的使用:

class SingletonType(type):
    def __init__(self,*args,**kwargs):
        super(SingletonType,self).__init__(*args,**kwargs)

    def __call__(cls, *args, **kwargs): # 這裡的cls,即Foo類
        print('cls',cls)
        obj = cls.__new__(cls,*args, **kwargs)
        cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj)
        return obj

class Foo(metaclass=SingletonType): # 指定建立Foo的type為SingletonType
    def __init__(self,name):
        self.name = name
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

obj = Foo('xx')

實現單例模式:

import threading

class SingletonType(type):
    _instance_lock = threading.Lock()
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            with SingletonType._instance_lock:
                if not hasattr(cls, "_instance"):
                    cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
        return cls._instance

class Foo(metaclass=SingletonType):
    def __init__(self,name):
        self.name = name


obj1 = Foo('name')
obj2 = Foo('name')
print(obj1,obj2)

結果:

參考:

https://www.runoob.com/design-pattern/singleton-pattern.html

https://www.cnblogs.com/chillax1314/articles/8287333.html

https://my.oschina.net/u/4051725/blog/4379773

https://www.cnblogs.com/ExMan/p/10426937.html