重要性無需多言,咱們在項目中常常有「要一個進程全局的變量(內存塊)」的需求,並且單例模式是幾種設計模式中最容易的。編程
我常用這種方式,由於簡單且不易出錯。設計模式
衆所周知,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
很少說,搜索引擎搜出來的結果,全都是推薦這種作法,讓人覺得這是「主流作法」(其實並非)。設計
這種作法,修改了 Singleton 的定義,即:全部變量共享一個內存塊,可是這個內存塊的內容是可變化的。而後經過將實例的 __dict__
方法重定向 class 的 __dict__
方法,以達到其目的。code
可是這種作法不是很符合我對 Singleton的感知,即:全局惟一,且其內容最好也不要變化。因此在實際開發中,並不喜歡這種作法。
一般來講,更好的方法則是:在應用代碼以外引入一個Manager,讓這個Manager來爲咱們建立和管理單例。這種作法也很廣泛,就是咱們一般所說的「依賴注入框架」。
若是使用 Spring,那麼一切都是很美妙的;若是沒有使用 Spring,我常用 Guice 來作個人依賴注入框架。
不過 Python 社區貌似對「依賴注入框架」、「IoC容器」等等都不怎麼感冒(實際上是好東西),在此先不提。
從我的的傾向來看,比較喜歡「掌握一種或兩種方式,而後使用最熟練,並且不出錯」的觀點,因此我只推薦第一二種方式。