單例模式的幾種實現方式
先來看幾個魔法方法的簡單運用:__new__, __init__, __call__。
class A(object):
def __init__(self, x):
print('x in __init__', x)
def __new__(cls, y): # 它只取下 cls 參數,並把其他參數傳給 __init__
print('y in __new__', y)
return super(A, cls).__new__(cls)
def __call__(self, z): # 允許一個類的實例像函數一樣被調用
print('z in __call__', z)
A('123')('abc') # 相當於先 a = A('123'), 再 a('abc')
# y in __new__ 123
# x in __init__ 123
# z in __call__ abc
1._new_(cls, *args, **kw) 方法實現
__new__ 構造方法至少需要一個 cls 參數,實例化時,解釋器會自動填入;
需要注意的是,new 方法中調用 new 方法時不要再調用自己的 new 方法,會報【超出最大遞歸深度】錯誤;要調用父類的 new 方法(默認調用)。
class Singleton(object):
_instance = None # 新建一個類屬性:需要使用類名前綴來訪問
def __new__(cls, *args, **kw): # 創建實例時,判斷是否已經存在,存在,返回,不存在,創建
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw) # 不要調用自己的new方法, 即: cls.__new__(), 會無限遞歸的;
return cls._instance # 返回實例對象
def __init__(self):
pass
sing1 = Singleton()
sing2 = Singleton()
sing1 is sing2 # true
"""
若 _instance = {}, 則
if cls not in cls._instance:
cls._instance[cls] = object.__new__(cls, *args, **kw)
"""
2.函數裝飾器實現:實例化前先調用這個裝飾器返回的 inner() 函數,再返回實例
用不可變的 類地址 作為鍵,其 實例 作為值,每次創造實例時,首先判斷該類是否存在實例,存在,直接返回該實例即可,否則新建一個實例並存放在字典中。
def singleton2(cls):
_instance = {} # 新建空字典; 不要_instance = None, 否則 inner 函數中賦值時,_instance 只會在函數作用域搜索,判斷時會報錯;
def inner(): # 判斷是否已有實例,如無,則新建一個實例並返回
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls] # 返回的是實例對象
return inner
@singleton2
class Cls():
def __init__(self):
pass # 可以照常添加屬性和方法
cls1 = Cls()
cls2 = Cls()
cls1 is cls2 # True
3.類裝飾器實現:類裝飾器可以直接依靠類內部的__call__方法來實現
Python 中,凡是可以將 () 直接應用到自身並執行,都稱為可調用對象。可調用對象包括自定義的函數、Python 內置函數以及類實例對象。
__call__ 方法可以使類實例對象可以像調用普通函數那樣,以 “對象名()” 的形式使用;此方法會在實例作為一個函數被 “調用” 時被調用;
如果定義了此方法,則 x(arg1, arg2, ...) 就相當於 x.__call__(arg1, arg2, ...) 的快捷方式。只要實現了__call__方法的對象都是可被調用對象。
class Singleton3(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] # 返回實例對象
@Singleton3
class Cls2(object):
def __init__(self):
pass
cls3 = Cls2()
cls4 = Cls2()
cls3 is cls4 # True
class Cls3():
pass
Cls33 = Singleton3(Cls3) # Cls3 傳給 __init__ 了;
cls5 = Cls33() # “名稱()” 可以理解為是 “名稱.__call__()” 的簡寫;
cls6 = Cls33()
cls5 is cls6 # True
4.metaless 元類實現
元類->類->實例,簡單來說,就是類是由元類創建的,type 是內置的元類,也可以自定義元類。
type(name, bases, dict):name 是類的名字,bases 是要繼承的父類集合,dict 是這個類的屬性。下面兩條語句會創建相同的 type 對象:
class X: a = 1
X = type('X', (object,), dict(a=1)) # 動態的創建類對象,dict(a=1) 也可以直接用 {'a':1, 'func_name': func_name}
class Singleton4(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton4, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Cls4(metaclass=Singleton4):
pass
cls1 = Cls4()
cls2 = Cls4()
cls1 is cls2 # True
5.import 實現
python 模塊是天然的單例模式:需要使用實例的時候,在文件中導入即可使用,從而減少相關實例的創建;
模塊在第一次導入時,會生成 .pyc 文件,當第二次導入時,就會直接加載 .pyc 文件,而不會再次執行模塊代碼;
因此,我們只需把相關的函數和數據定義在一個模塊中,就可以獲得一個單例對象了。如果我們真的想要一個單例類,可以考慮這樣做:
# my_singleton.py
class My_Singleton(object):
def foo(self):
print('My_Singleton')
mysingle = My_Singleton()
# other.py
from my_singleton import mysingle
mysingle.foo()
6.共享屬性
創建實例時, 把所有實例的 __dict__ 都指向同一個字典,這樣它們就會共享同一個屬性,具有相同的屬性和方法。
要查看對象中存儲的所有值,可檢查其 __dict__ 屬性。如果要確定對象是由什么組成的,應研究模塊 inspect。
注意:此時只是對象值相同,多個對象共享一個數據,依舊以最新的值為准,但不是同一個對象。
class Borg(object):
_state = {} # 共享屬性字典
def __new__(cls, *args, **kw):
ob = super(Borg, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state # 指向同一個字典
return ob
class MyClass2(Borg):
a = 1
應用場景簡介
每個實例都會占用資源,而且實例初始化會影響性能,這個時候就可以考慮使用單例模式,它給我們帶來的好處是只有一個實例占用資源,並且只需初始化一次;
當有同步需要的時候,可以通過一個實例來進行同步控制,比如對某個共享文件(如日志文件)的控制,對計數器的同步控制等,這種情況下由於只有一個實例,所以不用擔心同步問題。
模塊 logger 就是一個單例模式;Windows的資源管理器是一個單例模式;線程池,數據庫連接池等資源池一般也用單例模式;網站計數器;游戲場景管理器;
游戲中需要有 “場景管理器” 這樣一種東西,用來管理游戲場景的切換、資源載入、網絡連接等等任務。
這個管理器需要有多種方法和屬性,在代碼中很多地方會被調用,且被調用的必須是同一個管理器,否則既容易產生沖突,也會浪費內存資源。
某個服務器程序的配置信息存放在一個文件中,客戶端通過一個 AppConfig 的類來讀取配置文件的信息。如果在程序運行期間,有很多地方都需要使用配置文件的內容,
也就是說,很多地方都需要創建 AppConfig 對象的實例,這就導致系統中存在多個 AppConfig 的實例對象,而這樣會嚴重浪費內存資源,尤其是在配置文件內容很多的情況下。