Python中實現單例模式的n種方式和原理
在Python中如何實現單例模式?這可以說是一個經典的Python面試題了。這回我們講講實現Python中實現單例模式的n種方式,和它的原理。
什麼是單例模式
ofollow,noindex" target="_blank">維基百科 中說:
單例模式,也叫單子模式,是一種常用的軟體設計模式。在應用這個模式時,單例物件的類必須保證只有一個例項存在。許多時候整個系統只需要擁有一個的全域性物件,這樣有利於我們協調系統整體的行為。比如在某個伺服器程式中,該伺服器的配置資訊存放在一個檔案中,這些配置資料由一個單例物件統一讀取,然後服務程序中的其他物件再通過這個單例物件獲取這些配置資訊。這種方式簡化了在複雜環境下的配置管理。
在日常程式設計中,最常用的地方就在於配置類了。舉個例子:
from config import config print(config.SQLALCHEMY_DB_URI)
我們當然是希望config
在全域性中都是唯一的,那麼最簡單的實現單例的方式就出來了:使用一個全域性變數。
實現單例的方式
全域性變數
我們在一個模組中實現配置類:
# config.py class Config: def __init__(self, SQLALCHEMY_DB_URI): self.SQLALCHEMY_DB_URI = SQLALCHEMY_DB_URI config = Config("mysql://xxx")
當然這只是一個例子。真正實現的時候我們肯定不會這樣做,因為__init__
太難寫了。也許我們可以考慮 Python 3.7 中引入的dataclass
:
# config.py from dataclasses import dataclass @dataclass class Config: SQLALCHEMY_DB_URI = SQLALCHEMY_DB_URI config = Config(SQLALCHEMY_DB_URI ="mysql://")
通過使用全域性變數,我們在所有需要引用配置的地方,都使用from config import config
來匯入,這樣就達到了全域性唯一的目的。
使用metaclass
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 Config(metaclass=Singleton): def __init__(self, SQLALCHEMY_DB_URI): self.SQLALCHEMY_DB_URI = SQLALCHEMY_DB_URI
metaclass 是類的類,在Python中,instance是例項,class是類,metaclass是類的類。instance是class例項化的結果,而class是metaclass例項化的結果。因此,Config
在被例項化的時候,就會呼叫Singleton.__call__
, 所以所有Config()
的地方,最後都會返回同一個物件。
重寫__new__
class Singleton(object): _instance = None def __new__(class_, *args, **kwargs): if not isinstance(class_._instance, class_): class_._instance = object.__new__(class_, *args, **kwargs) return class_._instance class Config(Singleton, BaseClass): pass
Python中,類例項化的過程是先執行Config.__new__
生成例項,然後執行例項.__init__
進行初始化的,所以通過重寫__new__
也可以達到所有呼叫Config()
的地方都返回同一個物件。
使用裝飾器
def singleton(class_): class class_w(class_): _instance = None def __new__(class_, *args, **kwargs): if class_w._instance is None: class_w._instance = super(class_w, class_).__new__(class_, *args, **kwargs) class_w._instance._sealed = False return class_w._instance def __init__(self, *args, **kwargs): if self._sealed: return super(class_w, self).__init__(*args, **kwargs) self._sealed = True class_w.__name__ = class_.__name__ return class_w @singleton class Config(BaseClass): pass
使用裝飾器也能達到這樣的目的,即:有閉包儲存了例項,在每次呼叫Config()
之前,檢查該例項,如果已經初始化過,那麼就直接返回,否則則呼叫Config()
進行初始化,然後儲存。
總結
看完了這四種實現單例的方式,不知道你有沒有發現他們都有一個共同點,即:在真正呼叫Config()
之前進行一些攔截操作,來保證返回的物件都是同一個:
-
全域性變數:不直接呼叫
Config()
,而使用同一個全域性變數 -
使用metaclass:metaclass重寫
__call__
來保證每次呼叫Config()
都會返回同一個物件 -
重寫
__new__
:重寫__new__
來保證每次呼叫Config()
都會返回同一個物件 -
使用裝飾器:使用裝飾器來保證每次呼叫
Config()
都會返回同一個物件