1. 程式人生 > 其它 >python --單例模式

python --單例模式

單例模式以及Python實現

單例模式

單例模式就是確保一個類只有一個例項.當你希望整個系統中,某個類只有一個例項時,單例模式就派上了用場.
比如,某個伺服器的配置資訊存在在一個檔案中,客戶端通過AppConfig類來讀取配置檔案的資訊.如果程式的執行的過程中,很多地方都會用到配置檔案資訊,則就需要建立很多的AppConfig例項,這樣就導致記憶體中有很多AppConfig物件的例項,造成資源的浪費.其實這個時候AppConfig我們希望它只有一份,就可以使用單例模式.

實現單例模式的幾種方法

1. 使用模組


其實,python的模組就是天然的單例模式,因為模組在第一次匯入的時候,會生成.pyc檔案,當第二次匯入的時候,就會直接載入.pyc檔案,而不是再次執行模組程式碼.如果我們把相關的函式和資料定義在一個模組中,就可以獲得一個單例物件了.
新建一個python模組叫singleton,然後常見以下python檔案
mysingleton.py

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

使用:

from singleton.mysingleton import singleton

2. 使用裝飾器


裝飾器裡面的外層變數定義一個字典,裡面存放這個類的例項.當第一次建立的收,就將這個例項儲存到這個字典中.
然後以後每次建立物件的時候,都去這個字典中判斷一下,如果已經被例項化,就直接取這個例項物件.如果不存在就儲存到字典中.

# encoding:utf-8
__author__ = 'Fioman
' __time__ = '2019/3/6 10:22' def singleton(cls): # 單下劃線的作用是這個變數只能在當前模組裡訪問,僅僅是一種提示作用 # 建立一個字典用來儲存類的例項物件 _instance = {} def _singleton(*args, **kwargs): # 先判斷這個類有沒有物件 if cls not in _instance: _instance[cls] = cls(*args, **kwargs) # 建立一個物件,並儲存到字典當中 #
將例項物件返回 return _instance[cls] return _singleton @singleton class A(object): a = 1 def __init__(self, x=0): self.x = x print('這是A的類的初始化方法') a1 = A(2) a2 = A(3) print(id(a1), id(a2))

3.使用類


思路就是,呼叫類的instance方法,這樣有一個弊端就是在使用類建立的時候,並不是單例了.也就是說在建立類的時候一定要用類裡面規定的方法建立

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:06'


class Singleton(object):
    def __init__(self,*args,**kwargs):
        pass

    @classmethod
    def get_instance(cls, *args, **kwargs):
        # 利用反射,看看這個類有沒有_instance屬性
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance


s1 = Singleton()  # 使用這種方式建立例項的時候,並不能保證單例
s2 = Singleton.get_instance()  # 只有使用這種方式建立的時候才可以實現單例
s3 = Singleton()
s4 = Singleton.get_instance()

print(id(s1), id(s2), id(s3), id(s4))

注意,這樣的單例模式在單執行緒下是安全的,但是如果遇到多執行緒,就會出現問題.如果遇到多個執行緒同時建立這個類的例項的時候就會出現問題.

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:26'
import threading


class Singleton(object):
    def __init__(self, *args, **kwargs):
        pass

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance


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


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

 

執行結果好像也沒有問題,那是因為執行的速度足夠的快,如果在init()方法中有阻塞,就看到非常的明顯.

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:26'
import threading
import time

class Singleton(object):
    def __init__(self, *args, **kwargs):
        time.sleep(1)
        pass

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance


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


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

可以看到是建立了10個不同的例項物件,這是什麼原因呢.因為在一個物件建立的過程中,另外一個物件也建立了.當它判斷的時候,會先去獲取_instance屬性,因為這個時候還沒有,它就會呼叫init()方法.結果就是呼叫了10次,然後就建立了10個物件.

如何解決呢?
加鎖:
在哪裡加鎖呢?在獲取物件屬性_instance的時候加鎖,如果已經有人在獲取物件了,其他的人如果要獲取這個物件,就要等一哈.因為前面的那個人,可能在第一次建立物件.

建立物件的時候加鎖即可

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:38'

import time
import threading

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

    def __init__(self,*args,**kwargs):
        time.sleep(1)

    @classmethod
    def get_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

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

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

obj = Singleton.get_instance()
print(obj)

這種方式建立的單例,必須使用Singleton_get_instance()方法,如果使用Singleton()的話,得到的並不是單例.所以我們推薦使用__new__()方法來建立單例,這樣建立的單例可以使用類名()的方法進行例項化物件

4.基於__new__方法實現的單例模式(推薦使用,方便)


知識點:
1> 一個物件的例項化過程是先執行類的__new__方法,如果我們沒有寫,預設會呼叫object的__new__方法,返回一個例項化物件,然後再呼叫__init__方法,對這個物件進行初始化,我們可以根據這個實現單例.
2> 在一個類的__new__方法中先判斷是不是存在例項,如果存在例項,就直接返回,如果不存在例項就建立.

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 13:36'
import threading


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

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

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            with Singleton._instance_lock:
                if not hasattr(cls, '_instance'):
                    Singleton._instance = super().__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()

參考:https://www.jianshu.com/p/6a1690f0dd00