0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Python實(shí)現(xiàn)單例模式的五種寫法

馬哥Linux運(yùn)維 ? 來源:碼農(nóng)學(xué)習(xí)聯(lián)盟 ? 2023-05-24 11:27 ? 次閱讀

單例模式(Singleton Pattern)是一種常用的軟件設(shè)計(jì)模式,該模式的主要目的是確保某一個(gè)類只有一個(gè)實(shí)例存在。當(dāng)你希望在整個(gè)系統(tǒng)中,某個(gè)類只能出現(xiàn)一個(gè)實(shí)例時(shí),單例對(duì)象就能派上用場(chǎng)。

比如,某個(gè)服務(wù)器程序的配置信息存放在一個(gè)文件中,客戶端通過一個(gè) AppConfig 的類來讀取配置文件的信息。如果在程序運(yùn)行期間,有很多地方都需要使用配置文件的內(nèi)容,也就是說,很多地方都需要?jiǎng)?chuàng)建 AppConfig 對(duì)象的實(shí)例,這就導(dǎo)致系統(tǒng)中存在多個(gè) AppConfig 的實(shí)例對(duì)象,而這樣會(huì)嚴(yán)重浪費(fèi)內(nèi)存資源,尤其是在配置文件內(nèi)容很多的情況下。

事實(shí)上,類似 AppConfig 這樣的類,我們希望在程序運(yùn)行期間只存在一個(gè)實(shí)例對(duì)象。 在 Python 中,我們可以用多種方法來實(shí)現(xiàn)單例模式:

使用模塊

使用裝飾器

使用類

基于__new__方法實(shí)現(xiàn)

基于 metaclass 方式實(shí)現(xiàn)

下面來詳細(xì)介紹:

使用模塊 其實(shí),Python 的模塊就是天然的單例模式,因?yàn)槟K在第一次導(dǎo)入時(shí),會(huì)生成.pyc文件,當(dāng)?shù)诙螌?dǎo)入時(shí),就會(huì)直接加載.pyc文件,而不會(huì)再次執(zhí)行模塊代碼。

因此,我們只需把相關(guān)的函數(shù)和數(shù)據(jù)定義在一個(gè)模塊中,就可以獲得一個(gè)單例對(duì)象了。

如果我們真的想要一個(gè)單例類,可以考慮這樣做:

classSingleton(object):
deffoo(self):
pass
singleton=Singleton()

將上面的代碼保存在文件 mysingleton.py 中,要使用時(shí),直接在其他文件中導(dǎo)入此文件中的對(duì)象,這個(gè)對(duì)象即是單例模式的對(duì)象

frommysingletonimportsingleton

使用裝飾器

defSingleton(cls):
_instance={}

def_singleton(*args,**kargs):
ifclsnotin_instance:
_instance[cls]=cls(*args,**kargs)
return_instance[cls]

return_singleton


@Singleton
classA(object):
a=1

def__init__(self,x=0):
self.x=x


a1=A(2)
a2=A(3)

使用類

classSingleton(object):

def__init__(self):
pass

@classmethod
definstance(cls,*args,**kwargs):
ifnothasattr(Singleton,"_instance"):
Singleton._instance=Singleton(*args,**kwargs)
returnSingleton._instance

一般情況,大家以為這樣就完成了單例模式,但是當(dāng)使用多線程時(shí)會(huì)存在問題:

classSingleton(object):

def__init__(self):
pass

@classmethod
definstance(cls,*args,**kwargs):
ifnothasattr(Singleton,"_instance"):
Singleton._instance=Singleton(*args,**kwargs)
returnSingleton._instance

importthreading

deftask(arg):
obj=Singleton.instance()
print(obj)

foriinrange(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>

看起來也沒有問題,那是因?yàn)閳?zhí)行速度過快,如果在__init__方法中有一些 IO 操作,就會(huì)發(fā)現(xiàn)問題了。

下面我們通過time.sleep模擬,我們?cè)谏厦鎋_init__方法中加入以下代碼:

def__init__(self):
importtime
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ù)安全。

importtime
importthreading


classSingleton(object):
_instance_lock=threading.Lock()

def__init__(self):
time.sleep(1)

@classmethod
definstance(cls,*args,**kwargs):
withSingleton._instance_lock:
ifnothasattr(Singleton,"_instance"):
Singleton._instance=Singleton(*args,**kwargs)
returnSingleton._instance


deftask(arg):
obj=Singleton.instance()
print(obj)


foriinrange(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>

這樣就差不多了,但是還是有一點(diǎn)小問題,就是當(dāng)程序執(zhí)行時(shí),執(zhí)行了time.sleep(20)后,下面實(shí)例化對(duì)象時(shí),此時(shí)已經(jīng)是單例模式了。

但我們還是加了鎖,這樣不太好,再進(jìn)行一些優(yōu)化,把intance方法,改成下面這樣就行:

@classmethod
definstance(cls,*args,**kwargs):
ifnothasattr(Singleton,"_instance"):
withSingleton._instance_lock:
ifnothasattr(Singleton,"_instance"):
Singleton._instance=Singleton(*args,**kwargs)
returnSingleton._instance

這樣,一個(gè)可以支持多線程的單例模式就完成了。+

importtime
importthreading


classSingleton(object):
_instance_lock=threading.Lock()

def__init__(self):
time.sleep(1)

@classmethod
definstance(cls,*args,**kwargs):
ifnothasattr(Singleton,"_instance"):
withSingleton._instance_lock:
ifnothasattr(Singleton,"_instance"):
Singleton._instance=Singleton(*args,**kwargs)
returnSingleton._instance


deftask(arg):
obj=Singleton.instance()
print(obj)


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


time.sleep(20)
obj=Singleton.instance()
print(obj)

這種方式實(shí)現(xiàn)的單例模式,使用時(shí)會(huì)有限制,以后實(shí)例化必須通過obj = Singleton.instance()

如果用obj = Singleton(),這種方式得到的不是單例。

基于__new__方法實(shí)現(xiàn)

通過上面例子,我們可以知道,當(dāng)我們實(shí)現(xiàn)單例時(shí),為了保證線程安全需要在內(nèi)部加入鎖。

我們知道,當(dāng)我們實(shí)例化一個(gè)對(duì)象時(shí),是先執(zhí)行了類的__new__方法(我們沒寫時(shí),默認(rèn)調(diào)用object.__new__),實(shí)例化對(duì)象;然后再執(zhí)行類的__init__方法,對(duì)這個(gè)對(duì)象進(jìn)行初始化,所有我們可以基于這個(gè),實(shí)現(xiàn)單例模式。

importthreading


classSingleton(object):
_instance_lock=threading.Lock()

def__init__(self):
pass


def__new__(cls,*args,**kwargs):
ifnothasattr(Singleton,"_instance"):
withSingleton._instance_lock:
ifnothasattr(Singleton,"_instance"):
Singleton._instance=object.__new__(cls)
returnSingleton._instance

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

deftask(arg):
obj=Singleton()
print(obj)

foriinrange(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>

采用這種方式的單例模式,以后實(shí)例化對(duì)象時(shí),和平時(shí)實(shí)例化對(duì)象的方法一樣obj = Singleton()。

基于 metaclass 方式實(shí)現(xiàn)

相關(guān)知識(shí):

類由 type 創(chuàng)建,創(chuàng)建類時(shí),type 的__init__方法自動(dòng)執(zhí)行,類() 執(zhí)行 type 的__call__方法(類的__new__方法,類的__init__方法)

對(duì)象由類創(chuàng)建,創(chuàng)建對(duì)象時(shí),類的__init__方法自動(dòng)執(zhí)行,對(duì)象()執(zhí)行類的__call__方法

例子:

classFoo:
def__init__(self):
pass

def__call__(self,*args,**kwargs):
pass

obj=Foo()
#執(zhí)行type的__call__方法,調(diào)用 Foo類(是type的對(duì)象)的__new__方法,用于創(chuàng)建對(duì)象,然后調(diào)用 Foo類(是type的對(duì)象)的__init__方法,用于對(duì)對(duì)象初始化。

obj()#執(zhí)行Foo的__call__方法

元類的使用:

classSingletonType(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)
returnobj

classFoo(metaclass=SingletonType):#指定創(chuàng)建Foo的type為SingletonType
def__init__(self,name):
self.name=name
def__new__(cls,*args,**kwargs):
returnobject.__new__(cls)

obj=Foo('xx')

實(shí)現(xiàn)單例模式:

importthreading

classSingletonType(type):
_instance_lock=threading.Lock()
def__call__(cls,*args,**kwargs):
ifnothasattr(cls,"_instance"):
withSingletonType._instance_lock:
ifnothasattr(cls,"_instance"):
cls._instance=super(SingletonType,cls).__call__(*args,**kwargs)
returncls._instance

classFoo(metaclass=SingletonType):
def__init__(self,name):
self.name=name


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

鏈接:https://www.cnblogs.com/huchong/p/8244279.html





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • python
    +關(guān)注

    關(guān)注

    53

    文章

    4753

    瀏覽量

    84079

原文標(biāo)題:Python實(shí)現(xiàn)單例模式的五種寫法!

文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    談?wù)?b class='flag-5'>Python 中的迭代器模式

    中都得到了廣泛的應(yīng)用。 本文將談?wù)?Python 中的迭代器模式,主要內(nèi)容:什么是迭代器模式、Python 如何實(shí)現(xiàn)迭代器
    發(fā)表于 11-23 13:10 ?746次閱讀
    談?wù)?b class='flag-5'>Python</b> 中的迭代器<b class='flag-5'>模式</b>

    verilog語言寫法實(shí)現(xiàn)問題!求解答~

    在看verilog代碼時(shí),看到這樣兩表示方法:一是:“ wirea;assigna=b;”一是:“wirea=b;”請(qǐng)教各位大神這兩寫法
    發(fā)表于 01-29 14:33

    Python常用自增運(yùn)算寫法

    在學(xué)習(xí)任何一編程語言,運(yùn)算方法的學(xué)習(xí)是不可避免的,其中自增運(yùn)算符++的使用也是其中的重點(diǎn),很多人容易弄不明白其用法,尤其是在Python中,其用法更加的不同!我們可以寫個(gè)實(shí)例來說明一下++運(yùn)算符在
    發(fā)表于 04-09 17:45

    怎么通過運(yùn)動(dòng)檢測(cè)模式實(shí)現(xiàn)應(yīng)用產(chǎn)品的變革?

    通過審視運(yùn)動(dòng)檢測(cè)模式——加速度(包括平移運(yùn)動(dòng),如位置和方向)、振動(dòng)、沖擊、傾斜和旋轉(zhuǎn),各自的可能性,可以超越當(dāng)今大量MEMS應(yīng)用的范圍,極大地?cái)U(kuò)大應(yīng)用選項(xiàng)。MEMS加速度計(jì)和陀螺儀如何通過
    發(fā)表于 07-30 07:47

    Python驅(qū)動(dòng)程序實(shí)現(xiàn)對(duì)輪電機(jī)的速度控制

    在有了別人的代碼后,我就很容易的參考他的方法來實(shí)現(xiàn)電機(jī)的這個(gè)功能,因此我簡單的寫了一個(gè)Python程序,通過調(diào)用之前寫的Python驅(qū)動(dòng)程序,實(shí)現(xiàn)了對(duì)
    發(fā)表于 09-07 06:18

    PWM反饋控制模式研究

    PWM反饋控制模式研究
    發(fā)表于 08-08 10:46 ?20次下載
    <b class='flag-5'>五</b><b class='flag-5'>種</b>PWM反饋控制<b class='flag-5'>模式</b>研究

    模式詳解

    是private的話,會(huì)不更好的封裝該內(nèi)呢?我主要是應(yīng)用在使用普通類模擬枚舉類型里,后來發(fā)現(xiàn)這就是傳說中的模式。構(gòu)造函數(shù)弄成private 就是
    發(fā)表于 09-27 15:10 ?0次下載

    模式分成餓漢模式和懶漢模式解析

    定義:作為對(duì)象的創(chuàng)建模式,模式確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。這個(gè)類稱為
    發(fā)表于 12-11 09:49 ?1614次閱讀

    Python下劃線的模式和命名約定

    本文介紹了Python下劃線和雙下劃線("dunder")的各種含義和命名約定,名稱修飾(name mangling)的工作原理,以及它如何影響你自己的Python類。
    的頭像 發(fā)表于 12-30 16:40 ?1957次閱讀
    <b class='flag-5'>Python</b><b class='flag-5'>五</b><b class='flag-5'>種</b>下劃線的<b class='flag-5'>模式</b>和命名約定

    Python略高級(jí)特征的使用方法

    Python 多好用不用多說,大家看看自己用的語言就知道了。但是 Python 隱藏的高級(jí)功能你都 get 了嗎?本文中,作者列舉了 Python
    的頭像 發(fā)表于 04-24 10:12 ?3130次閱讀

    開關(guān)電源PWM 反饋控制模式

    開關(guān)電源PWM 反饋控制模式(新型電源技術(shù)作業(yè)答案)-開關(guān)電源PWM 反饋控制模式
    發(fā)表于 09-27 10:01 ?76次下載
    開關(guān)電源PWM <b class='flag-5'>五</b><b class='flag-5'>種</b>反饋控制<b class='flag-5'>模式</b>

    詳解golang事務(wù)的三寫法

    本文中作者展示了 golang 事務(wù)的三寫法
    的頭像 發(fā)表于 03-16 08:52 ?5186次閱讀

    60個(gè)python日常高頻寫法

    今天給大家準(zhǔn)備了60個(gè)python日常高頻寫法,如果覺得有用,那就點(diǎn)贊收藏起來吧~
    的頭像 發(fā)表于 04-16 09:49 ?930次閱讀

    模式(Singleton Pattern)實(shí)現(xiàn)的方法

    模式是指某個(gè)類只允許存在一個(gè)實(shí)例對(duì)象,例如在打印機(jī)打印文件時(shí),一次只允許打印一個(gè)文件
    的頭像 發(fā)表于 06-01 11:44 ?547次閱讀
    <b class='flag-5'>單</b><b class='flag-5'>例</b><b class='flag-5'>模式</b>(Singleton Pattern)<b class='flag-5'>實(shí)現(xiàn)</b>的方法

    insert into 語句的三寫法

    INSERT INTO是MySQL中常用的一SQL語句,用于將數(shù)據(jù)插入到表中。此文將詳細(xì)介紹INSERT INTO語句的三不同寫法及其用途,并提供代碼示例和相關(guān)解釋。 正文: 一、基本插入
    的頭像 發(fā)表于 11-21 14:18 ?1w次閱讀