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

        共 8825字,需瀏覽 18分鐘

         ·

        2022-09-06 09:01

        來源: https://www.cnblogs.com/huchong/p/8244279.html

        推薦閱讀: 終于來了, ?彭濤Python 爬蟲訓(xùn)練營 !

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

        比如,某個(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)單例模式:

        1. 使用模塊
        2. 使用裝飾器
        3. 使用類
        4. 基于?__new__?方法實(shí)現(xiàn)
        5. 基于 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è)單例類,可以考慮這樣做:

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

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

            
            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)使用多線程時(shí)會(huì)存在問題:

            
            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>

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

        下面我們通過?time.sleep?模擬,我們?cè)谏厦?__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>

        這樣就差不多了,但是還是有一點(diǎn)小問題,就是當(dāng)程序執(zhí)行時(shí),執(zhí)行了?time.sleep(20)?后,下面實(shí)例化對(duì)象時(shí),此時(shí)已經(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

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

            
            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)

        這種方式實(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)單例模式。

            
            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>

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

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

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

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

        例子:

            
            class?Foo:
        ????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__?方法????

        元類的使用:

            
            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')

        實(shí)現(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)

        如果你覺得文章還不錯(cuò),請(qǐng)大家?點(diǎn)贊、分享、留言?下,因?yàn)檫@將是我持續(xù)輸出更多優(yōu)質(zhì)文章的最強(qiáng)動(dòng)力!

        另外,我們還開發(fā)了一門爬蟲課,從0到1帶大家學(xué)習(xí) Python 爬蟲, 既可以做副業(yè)接單,也可以做爬蟲工程師,歡迎掃碼了解。

        具體看:終于來了, ?彭濤Python 爬蟲訓(xùn)練營 !


        長按掃碼,可以試聽,想了解爬蟲更多細(xì)節(jié)可以添加「257735


        瀏覽 50
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 超碰9999 | 嗯嗯嗯啊啊啊av A片 XXXX受爽视频 | 亚洲AV无码一区二区三区少妇 | www.av黄色电影.co m | 挨操网 |