Python 中 Singleton 的寫法及其拓展

爲什麼要有 Singleton ?

重要性無需多言,咱們在項目中常常有「要一個進程全局的變量(內存塊)」的需求,並且單例模式是幾種設計模式中最容易的。編程

偷懶且有用的作法:模塊級別常量

我常用這種方式,由於簡單且不易出錯。設計模式

衆所周知,Python 的 module 概念,是一個自然的 Singleton。並且 Python 是多範式語言,能夠沒必要像 Java 那樣使用 class 去處理這件事情,在 module 級別定義一個常量,是一種很天然的想法。bash

代碼以下:框架

  • 定義方
singleton.py

class _MySingleton(object):
    """ 咱們使用下劃線開頭, 告誡調用者, 不要直接 new 也不要來訪問這個class """
    def __init__(self, name, age):
        self._name = name
        self._age = age

    def print_name(self):
        print(self._name)

# 能夠定製多個全局實例
S1 = _MySingleton('s1', 22)
S2 = _MySingleton('s2', 11)
複製代碼
  • 調用方
caller.py

from singleton import S1

# 盡情使用S1( 在任意點import 均可以 ), 它是全局惟一的!
複製代碼

正規作法:元編程

有些人不喜歡上面那種作法,他們認爲「破壞了代碼的純粹性」,這個時候咱們可使用元編程的方式,讓咱們更近一步。函數

這是從 Python Cookbook 摘取出來的代碼。ui

  • 元類基類
class SingletonMetaclass(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(SingletonMetaclass, self).__call__(*args, **kwargs)
            return self.__instance
        else:
            return self.__instance
複製代碼
  • 繼承了元類基類的類
class SpamSingleton(metaclass=SingletonMetaclass):
    """注意: 根據Singleton的定義, 構造函數通常須要使用默認的構造函數"""
    def get_addr(self):
        return id(self)
複製代碼
  • 調用方
from singleton import SpamSingleton

s1 = SpamSingleton()
s2 = SpamSingleton()

pritn(id(s1), id(s2))   # 內存地址是同樣的
複製代碼

老實說,理解 SingletonMetaclass 的做用過程仍是有點困難的,我花了很久才搞清楚上面各個方法的調用流程。搜索引擎

不過 SingletonMetaclass 的做用也是巨大的,咱們定義將其放入一個 base.py 的文件中,任什麼時候刻咱們想要定義某個 Singleton class,直接從其繼承便可,簡單且方便。spa

比較 Hack 的作法:Borg 模式

很少說,搜索引擎搜出來的結果,全都是推薦這種作法,讓人覺得這是「主流作法」(其實並非)。設計

這種作法,修改了 Singleton 的定義,即:全部變量共享一個內存塊,可是這個內存塊的內容是可變化的。而後經過將實例的 __dict__ 方法重定向 class 的 __dict__ 方法,以達到其目的。code

可是這種作法不是很符合我對 Singleton的感知,即:全局惟一,且其內容最好也不要變化。因此在實際開發中,並不喜歡這種作法。

更進一步

一般來講,更好的方法則是:在應用代碼以外引入一個Manager,讓這個Manager來爲咱們建立和管理單例。這種作法也很廣泛,就是咱們一般所說的「依賴注入框架」。

若是使用 Spring,那麼一切都是很美妙的;若是沒有使用 Spring,我常用 Guice 來作個人依賴注入框架。

不過 Python 社區貌似對「依賴注入框架」、「IoC容器」等等都不怎麼感冒(實際上是好東西),在此先不提。

總結

從我的的傾向來看,比較喜歡「掌握一種或兩種方式,而後使用最熟練,並且不出錯」的觀點,因此我只推薦第一二種方式。

相關文章
相關標籤/搜索