Python 實現(xiàn)單例模式的五種寫法
推薦閱讀: 終于來了, ?彭濤Python 爬蟲訓(xùn)練營 !
單例模式(Singleton Pattern)?是一種常用的軟件設(shè)計模式,該模式的主要目的是確保某一個類只有一個實例存在。當(dāng)你希望在整個系統(tǒng)中,某個類只能出現(xiàn)一個實例時,單例對象就能派上用場。
比如,某個服務(wù)器程序的配置信息存放在一個文件中,客戶端通過一個 AppConfig 的類來讀取配置文件的信息。如果在程序運行期間,有很多地方都需要使用配置文件的內(nèi)容,也就是說,很多地方都需要創(chuàng)建 AppConfig 對象的實例,這就導(dǎo)致系統(tǒng)中存在多個 AppConfig 的實例對象,而這樣會嚴(yán)重浪費內(nèi)存資源,尤其是在配置文件內(nèi)容很多的情況下。
事實上,類似 AppConfig 這樣的類,我們希望在程序運行期間只存在一個實例對象。
在 Python 中,我們可以用多種方法來實現(xiàn)單例模式:
- 使用模塊
- 使用裝飾器
- 使用類
-
基于?
__new__?方法實現(xiàn) - 基于 metaclass 方式實現(xiàn)
下面來詳細(xì)介紹:
使用模塊
其實,Python 的模塊就是天然的單例模式,因為模塊在第一次導(dǎo)入時,會生成?.pyc?文件,當(dāng)?shù)诙螌?dǎo)入時,就會直接加載?.pyc?文件,而不會再次執(zhí)行模塊代碼。
因此,我們只需把相關(guān)的函數(shù)和數(shù)據(jù)定義在一個模塊中,就可以獲得一個單例對象了。
如果我們真的想要一個單例類,可以考慮這樣做:
class?Singleton(object):
????def?foo(self):
????????pass
singleton?=?Singleton()
將上面的代碼保存在文件 mysingleton.py 中,要使用時,直接在其他文件中導(dǎo)入此文件中的對象,這個對象即是單例模式的對象
from?mysingleton?import?singleton
使用裝飾器
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)
使用類
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
一般情況,大家以為這樣就完成了單例模式,但是當(dāng)使用多線程時會存在問題:
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()
程序執(zhí)行后,打印結(jié)果如下:
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
<__main__.Singleton?object?at?0x02C933D0>
看起來也沒有問題,那是因為執(zhí)行速度過快,如果在?__init__?方法中有一些 IO 操作,就會發(fā)現(xiàn)問題了。
下面我們通過?time.sleep?模擬,我們在上面?__init__?方法中加入以下代碼:
def?__init__(self):
????import?time
????time.sleep(1)
重新執(zhí)行程序后,結(jié)果如下:
<__main__.Singleton?object?at?0x034A3410>
<__main__.Singleton?object?at?0x034BB990>
<__main__.Singleton?object?at?0x034BB910>
<__main__.Singleton?object?at?0x034ADED0>
<__main__.Singleton?object?at?0x034E6BD0>
<__main__.Singleton?object?at?0x034E6C10>
<__main__.Singleton?object?at?0x034E6B90>
<__main__.Singleton?object?at?0x034BBA30>
<__main__.Singleton?object?at?0x034F6B90>
<__main__.Singleton?object?at?0x034E6A90>
問題出現(xiàn)了!按照以上方式創(chuàng)建的單例,無法支持多線程。
解決辦法:加鎖!未加鎖部分并發(fā)執(zhí)行,加鎖部分串行執(zhí)行,速度降低,但是保證了數(shù)據(jù)安全。
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)
打印結(jié)果如下:
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
<__main__.Singleton?object?at?0x02D6B110>
這樣就差不多了,但是還是有一點小問題,就是當(dāng)程序執(zhí)行時,執(zhí)行了?time.sleep(20)?后,下面實例化對象時,此時已經(jīng)是單例模式了。
但我們還是加了鎖,這樣不太好,再進(jìn)行一些優(yōu)化,把?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
這樣,一個可以支持多線程的單例模式就完成了。+
import?time
import?threading
class?Singleton(object):
????_instance_lock?=?threading.Lock()
????def?__init__(self):
????????time.sleep(1)
????@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
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)
這種方式實現(xiàn)的單例模式,使用時會有限制,以后實例化必須通過?obj = Singleton.instance()
如果用?obj = Singleton(),這種方式得到的不是單例。
基于?__new__?方法實現(xiàn)
通過上面例子,我們可以知道,當(dāng)我們實現(xiàn)單例時,為了保證線程安全需要在內(nèi)部加入鎖。
我們知道,當(dāng)我們實例化一個對象時,是先執(zhí)行了類的?__new__?方法(我們沒寫時,默認(rèn)調(diào)用?object.__new__),實例化對象;然后再執(zhí)行類的?__init__?方法,對這個對象進(jìn)行初始化,所有我們可以基于這個,實現(xiàn)單例模式。
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()
打印結(jié)果如下:
<__main__.Singleton?object?at?0x038B33D0>?<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
<__main__.Singleton?object?at?0x038B33D0>
采用這種方式的單例模式,以后實例化對象時,和平時實例化對象的方法一樣?obj = Singleton()?。
基于 metaclass 方式實現(xiàn)
相關(guān)知識:
-
類由 type 創(chuàng)建,創(chuàng)建類時,type 的?
__init__?方法自動執(zhí)行,類() 執(zhí)行 type 的?__call__?方法(類的?__new__?方法,類的?__init__?方法) -
對象由類創(chuàng)建,創(chuàng)建對象時,類的?
__init__?方法自動執(zhí)行,對象()執(zhí)行類的?__call__?方法
例子:
class?Foo:
????def?__init__(self):
????????pass
????def?__call__(self,?*args,?**kwargs):
????????pass
obj?=?Foo()
#?執(zhí)行type的?__call__?方法,調(diào)用 Foo類(是type的對象)的?__new__方法,用于創(chuàng)建對象,然后調(diào)用 Foo類(是type的對象)的?__init__方法,用于對對象初始化。
obj()????#?執(zhí)行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):?#?指定創(chuàng)建Foo的type為SingletonType
????def?__init__(self,name):
????????self.name?=?name
????def?__new__(cls,?*args,?**kwargs):
????????return?object.__new__(cls)
obj?=?Foo('xx')
實現(xiàn)單例模式:
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)
如果你覺得文章還不錯,請大家?點贊、分享、留言?下,因為這將是我持續(xù)輸出更多優(yōu)質(zhì)文章的最強動力!
另外,我們還開發(fā)了一門爬蟲課,從0到1帶大家學(xué)習(xí) Python 爬蟲, 既可以做副業(yè)接單,也可以做爬蟲工程師,歡迎掃碼了解。
具體看:終于來了, ?彭濤Python 爬蟲訓(xùn)練營 !
長按掃碼,可以試聽,想了解爬蟲更多細(xì)節(jié)可以添加「257735」
