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

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

來自專欄 Crossin的編程教室

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

因此對於這種情況,有種更好的實現方式:

單例(Singleton)

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

單例模式保證了在程序的不同位置都可以且僅可以取到同一個對象實例:如果實例不存在,會創建一個實例;如果已存在就會返回這個實例。因為單例是一個類,所以你也可以為其提供相應的操作方法,以便於對這個實例進行管理。

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

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

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

以下是實現方法索引:

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

使用函數裝飾器實現單例

以下是實現代碼:

def singleton(cls): _instance = {} def inner(): if cls not in _instance: _instance[cls] = cls() return _instance[cls] return inner @singletonclass Cls(object): def __init__(self): passcls1 = 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]@Singletonclass Cls2(object): def __init__(self): passcls1 = Cls2()cls2 = Cls2()print(id(cls1) == id(cls2))

同時,由於是面對對象的,這裡還可以這麼用

class Cls3(): passCls3 = 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?

    stackoverflow.com/quest
  • python-__new__-magic-method-explained

    howto.lintel.in/python-
  • Why is __init__() always called after __new__()?

    stackoverflow.com/quest

使用 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): passsingle1 = 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): passcls1 = 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的編程教室


推薦閱讀:

20 多人問我怎麼入門編程,最久的堅持了 2 個月
編程入門(一):Hello World!
Android開發學習應該先學什麼?
Teach Yourself Computer Science
hello, world——《例C》(二)

TAG:Python | 設計模式 | 編程入門 |