單例模式 python 的實現
阿新 • • 發佈:2021-07-02
單例模式
單例模式就是確保一個類只有一個例項.當你希望整個系統中,某個類只有一個例項時,單例模式就派上了用場.
比如,某個伺服器的配置資訊存在在一個檔案中,客戶端通過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 singleton2. 使用裝飾器
裝飾器裡面的外層變數定義一個字典,裡面存放這個類的例項.當第一次建立的收,就將這個例項儲存到這個字典中.
然後以後每次建立物件的時候,都去這個字典中判斷一下,如果已經被例項化,就直接取這個例項物件.如果不存在就儲存到字典中.
# from singleton.mysingleton import singleton# from mysite.testDir.mysingleton import singleton # singleton.foo() # encoding:utf-8 __author__ = 'Fioman' __time__ = '2019/3/6 10:22' def singleton(cls): # 單下劃線的作用是這個變數只能在當前模組裡訪問,僅僅是一種提示作用 # 建立一個字典用來儲存類的例項物件 _instance = {} def _singleton(*args, **kwargs): # 先判斷這個類有沒有物件 ifcls 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)) #################### 這是A的類的初始化方法 53009104 53009104
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()
<__main__.Singleton object at 0x03A04870><__main__.Singleton object at 0x03A046B0> <__main__.Singleton object at 0x03A04550><__main__.Singleton object at 0x03A044D0> <__main__.Singleton object at 0x0391C170> <__main__.Singleton object at 0x03A04B10> <__main__.Singleton object at 0x03A04A30> <__main__.Singleton object at 0x03A04950> <__main__.Singleton object at 0x03A04790><__main__.Singleton object at 0x03A045D0>
可以看到是建立了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
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。