Python單例模式(Singleton)的N種實現

不少初學者喜歡用 全局變量 ,由於這比函數的參數傳來傳去更容易讓人理解。確實在不少場景下用全局變量很方便。不過若是代碼規模增大,而且有多個文件的時候,全局變量就會變得比較混亂。你可能不知道在哪一個文件中定義了相同類型甚至重名的全局變量,也不知道這個變量在程序的某個地方被作了怎樣的操做。python

所以對於這種狀況,有種更好的實現方式:
單例(Singleton)面試

單例是一種 設計模式 ,應用該模式的類只會生成一個實例。算法

單例模式保證了在程序的不一樣位置都 能夠且僅能夠取到同一個對象實例 :若是實例不存在,會建立一個實例;若是已存在就會返回這個實例。由於單例是一個類,因此你也能夠爲其提供相應的操做方法,以便於對這個實例進行管理。編程

舉個例子來講,好比你開發一款遊戲軟件,遊戲中須要有「場景管理器」這樣一種東西,用來管理遊戲場景的切換、資源載入、網絡鏈接等等任務。這個管理器須要有多種方法和屬性,在代碼中不少地方會被調用,且被調用的必須是同一個管理器,不然既容易產生衝突,也會浪費資源。這種狀況下,單例模式就是一個很好的實現方法。設計模式

單例模式普遍應用於各類開發場景,對於開發者而言是必須掌握的知識點,同時在不少面試中,也是常見問題。本篇文章總結了目前主流的實現單例模式的方法供讀者參考。網絡

但願看過此文的同窗,在之後被面到此問題時,能直接皮一下面試官,「我會 4 種單例模式實現,你想聽哪種?」函數

如下是實現方法索引:學習

  • 使用函數裝飾器實現單例
  • 使用類裝飾器實現單例
  • 使用 new 關鍵字實現單例
  • 使用 metaclass 實現單例

使用函數裝飾器實現單例

如下是實現代碼:區塊鏈

def singleton(cls):
    _instance = {}

    def inner():
        if cls not in _instance:
            _instance[cls] = cls()
        return _instance[cls]
    return inner
    
@singleton
class Cls(object):
    def __init__(self):
        pass

cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))

輸出結果:搜索引擎

True

在 Python 中,id 關鍵字可用來查看對象在內存中的存放位置,這裏 cls1 和 cls2 的 id 值相同,說明他們指向了同一個對象。

關於裝飾器的知識,有不明白的同窗能夠查看以前的文章 【編程課堂】裝飾器淺析 或者使用搜索引擎再學習一遍。代碼中比較巧妙的一點是:

_instance = {}

使用不可變的 類地址 做爲鍵,其實例做爲值,每次創造實例時,首先查看該類是否存在實例,存在的話直接返回該實例便可,不然新建一個實例並存放在字典中。

使用類裝飾器實現單例

代碼:

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 Cls2(object):
    def __init__(self):
        pass

cls1 = Cls2()
cls2 = Cls2()
print(id(cls1) == id(cls2))

同時,因爲是面對對象的,這裏還能夠這麼用

class Cls3():
    pass

Cls3 = Singleton(Cls3)
cls3 = Cls3()
cls4 = Cls3()
print(id(cls3) == id(cls4))

使用 類裝飾器實現單例的原理和 函數裝飾器 實現的原理類似,理解了上文,再理解這裏應該不難。

New、Metaclass 關鍵字

在接着說另外兩種方法以前,須要瞭解在 Python 中一個類和一個實例是經過哪些方法以怎樣的順序被創造的。

簡單來講, 元類 ( metaclass ) 能夠經過方法 metaclass 創造了 類(class) ,而 類(class) 經過方法 new 創造了 實例(instance)

在單例模式應用中,在創造類的過程當中或者創造實例的過程當中稍加控制達到最後產生的實例都是一個對象的目的。

本文主講單例模式,因此對這個 topic 只會點到爲止,有感興趣的同窗能夠在網上搜索相關內容,幾篇參考文章:

  • What are metaclasses in Python?

https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python

  • python-__new__-magic-method-explained

http://howto.lintel.in/python-__new__-magic-method-explained/

  • Why is __init__() always called after __new__()?

https://stackoverflow.com/questions/674304/why-is-init-always-called-after-new

使用 new 關鍵字實現單例模式

使用 new 方法在創造實例時進行干預,達到實現單例模式的目的。

class Single(object):
    _instance = None
    def __new__(cls, *args, **kw):
        if cls._instance is None:
            cls._instance = object.__new__(cls, *args, **kw)
        return cls._instance
    def __init__(self):
        pass

single1 = Single()
single2 = Single()
print(id(single1) == id(single2))

在理解到 new 的應用後,理解單例就不難了,這裏使用了

_instance = None

來存放實例,若是 _instance 爲 None,則新建實例,不然直接返回 _instance 存放的實例。

使用 metaclass 實現單例模式

一樣,咱們在類的建立時進行干預,從而達到實現單例的目的。

在實現單例以前,須要瞭解使用 type 創造類的方法,代碼以下:

def func(self):
    print("do sth")

Klass = type("Klass", (), {"func": func})

c = Klass()
c.func()

以上,咱們使用 type 創造了一個類出來。這裏的知識是 mataclass 實現單例的基礎。

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Cls4(metaclass=Singleton):
    pass

cls1 = Cls4()
cls2 = Cls4()
print(id(cls1) == id(cls2))

這裏,咱們將 metaclass 指向 Singleton 類,讓 Singleton 中的 type 來創造新的 Cls4 實例。

小結

本文雖然是講單例模式,但在實現單例模式的過程當中,涉及到了蠻多高級 Python 語法,包括裝飾器、元類、new、type 甚至 super 等等。對於新手同窗可能難以理解,其實在工程項目中並不須要你掌握的面面俱到,掌握其中一種,剩下的做爲了解便可。

by 周鑫鑫

關於更多的設計模式,給初學者推薦《 Head First 設計模式 》(Head First Design Patterns),此書淺顯易懂,在 Head First 系列書籍裏面也算是很好的一本。

咱們的資源網盤裏有電子版,獲取地址請在公衆號( Crossin的編程教室 )裏回覆關鍵字: 資源

════
其餘文章及回答:

如何自學Python | 新手引導 | 精選Python問答 | Python單詞表 | 區塊鏈 | 人工智能 | 雙11 | 嘻哈 | 爬蟲 | 排序算法

歡迎搜索及關注: Crossin的編程教室

相關文章
相關標籤/搜索