python-單例模式趣話

一天,小猿(程序猿)正在安靜的寫代碼(製造bug),突然狂風大作,只見,小猿的電腦屏幕出現一個無底的黑洞,小猿被捲入電腦,跌入洞中……

不知過了多久,小猿醒了過來,緩緩的睜開眼,發現自己躺在一個陌生的地方,而此時他眼前走過的是一個個機器人,這些機器人背後都有代號,有文件、內存、磁盤等等。小猿還沒來得及驚慌,就被一羣背後寫着“進程”的機器人組織開會,只見臺上坐着一個大佬,背後寫着赫赫的三個大字母:CPU,小猿心想,這是進了計算機內部窩了啊,望着身邊一個個面無表情的機器人,瞬間感覺弱小、可憐、無助、又飢餓……

原來,這位CPU大佬想做一個應用, 在整個應用的運行過程中,一直保持並維護着唯一的實例。所以組織大家集結思路,誰能做到,承諾一頓大餐作爲獎勵。小心想,這不就是單例嗎,就這?能難倒我?於是小本着助人爲樂(能喫飽飯)的原則,踊躍發言並給出了實現方案:

1.使用裝飾器方式實現

1.1函數裝飾器方式

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):
    def __init__(self, a=0):
        self.a = a


a1 = A(1)
a2 = A(2)
# id()函數可以獲取對象的內存地址,同一內存地址即爲同一對象
print(id(a1), id(a2))

1.2類裝飾器方式

class Singleton(object):
    def __init__(self, cls):
        self._cls = cls
        self._instance = {}

    def __call__(self):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]


@Singleton
class B(object):
    def __init__(self):
        pass


b1 = B()
b2 = B()
print(id(b1), id(b2))

CPU大佬看了搖了搖頭,若有所思,說,這樣是可以實現,但是裝飾器的原理和執行順序很複雜,我不能接受。

小猿反駁說:裝飾器是基於面向切面編程思想來實現的,具有很高的解耦性和靈活性,而且本質其實不難理解,裝飾器本質上其實還是一個python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。裝飾器的本質是函數,主要用來裝飾其他函數,也就是爲其他函數增加功能……

”打住打住“,沒等小說完,CPU就打斷了他,“我不想聽你這些繁瑣的解釋,還有其他實現方式嗎,沒有就退下吧!”

心想:這真是一個難纏的人啊!爲了面子(主要還是想喫飯),忍了,給出方案2

2.使用類的方式實現

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

    @classmethod
    def get_instance(cls, *args, **kwargs):
        # hasattr() 函數用於判斷對象是否包含對應的屬性,這裏是看看這個類有沒有_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))

小猿解釋道: 這種方式的思路就是,調用類的get_instance方法去創建對象,get_instance方法會判斷之前有沒有創建過對象,有的話也是會返回之前已經創建的對象,不再新創建,但是這樣有一個弊端,就是在使用類創建(s3 = Singleton()這種方式)的時候,就不能保證單例了,也就是說在創建類的時候一定要用類裏面規定的get_instance方法創建。

CPU說到:“那你解決一下嘍”。

小猿又開始在心裏嘀咕:站着說話不腰疼,我要不是看上那頓大餐,我纔不理你呢!給你方案3

3.使用__new__函數實現

class Singleton(object):

    def __init__(self):
        print("__init__")

    def __new__(cls, *args, **kwargs):
        print("__new__")
        if not hasattr(Singleton, "_instance"):
            print("創建新實例")
            Singleton._instance = object.__new__(cls)
        return Singleton._instance


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

當python實例化一個對象時,是先執行類的__new__方法,當我們沒寫__new__方法時,默認調用基類object的__new__方法然後再執行類的__init__方法,對這個對象進行初始化,所有我們可以基於這個,去實現單例模式 ,我們通過hasattr(Singleton, "_instance")(其中hasattr()的功能是判斷一個對象有沒有指定的屬性)去判斷之前有沒有實例化過對象,如果有,就直接返回,沒有就新創建一個。

附上控制檯輸出:可以看出,同樣實現了單例。

這時CPU大佬說到:”看輸出其實執行了兩遍__init__方法,既然是同一個對象,初始化兩次,這不合理。“

”沒關係,那優化一下!“小猿說。

class Singleton(object):

    def __init__(self):
        if not hasattr(Singleton, "_first_init"):
            print("__init__")
            Singleton._first_init = True

    def __new__(cls, *args, **kwargs):
        print("__new__")
        if not hasattr(Singleton, "_instance"):
            print("創建新實例")
            Singleton._instance = object.__new__(cls)
        return Singleton._instance

通過控制檯輸出我們可以看到,__init__方法只執行了一次。

到這時,大佬似乎有些滿意。小猿又說道: ”爲了保證線程安全,我們還可以在類內部加入鎖機制!

import threading


class Singleton(object):
    _instance_lock = threading.Lock() # 線程鎖

    def __init__(self):
        if not hasattr(Singleton, "_first_init"):
            print("__init__")
            Singleton._first_init = True

    def __new__(cls, *args, **kwargs):
        print("__new__")
        with Singleton._instance_lock:
            if not hasattr(Singleton, "_instance"):
                print("創建新實例")
                Singleton._instance = object.__new__(cls)
        return Singleton._instance


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


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

此時,CPU大佬露出了欣慰的笑容,大讚,衆機器人也都歡呼着,CPU大佬也兌現了大餐的承諾,讓人帶着小猿去喫大餐。

小猿看到滿桌子的山珍海味,開心的合不攏嘴,大口大口地吃了起來……

叮鈴鈴,叮鈴鈴,鬧鈴響了……原來這一切只是小猿做的夢,而此時小猿的嘴裏還咬着一隻襪子……哈哈哈

藉此,梳理開發中經常用到的單例模式,其實python實現單例的方式還有很多,像通過模塊、metaclass方式等等,這裏不一一實現,感興趣的朋友可以自行百度。

最後,感謝女朋友在生活中,工作上的包容、理解與支持 !

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章