無論是在python代碼中,還是面試中單例設計模式都是經常被問到和使用的,例如面試中會讓你用代碼實現單例模式分幾種不同的方式,或者問你在平常工作中哪些地方有用到單例設計模式,然后深入探討。
在本文中我將針對這兩個問題來回答和用python代碼來編寫我們的單例模式。
首先,我們要了解什么是單例模式--官方解釋是:確保一個類只有一個實例(也就是類的對象),並且提供一個全局的訪問點(外部通過這個訪問點來訪問該類的唯一實例)。通俗的說就是,無論類實例化多少次,都只有一個相同的對象產生,並且可以通過一個具柄去訪問這個這個唯一實例。
其次,我們平時代碼中碰到是用單例模式設計的代碼有哪些呢?
- 應用框架的配置文件config(如flask,django框架的配置文件)
- 線程池,數據庫連接池的創建
- 應用程序的日志應用
這些應用都有一個共性那就是:他們都是全局共享的對象不會發生變化,不希望重復創建,這樣有利於節省空間和減少性能消耗。
python實現單例模式的幾種方式分別有:
一、類方法實現
class SingleType: _instance = None @classmethod def instance(cls): if not cls._instance: cls._instance = SingleType() return cls._instance for i in range(100): print(SingleType.instance())
看一下打印結果:
返回的都是同一個對象,實現了我們說的,無論調用多少次,都只返回同一個實例,但是這在多線程的環境中,可能會存在返回多個實例,我們實驗一下。
import threading
import time
class SingleType:
_instance = None
def __init__(self):
time.sleep(1)
super().__init__()
@classmethod
def instance(cls):
if not cls._instance:
cls._instance = SingleType()
return cls._instance
def task():
obj = SingleType.instance()
print(obj)
for i in range(10):
t = threading.Thread(target=task).start()
print(SingleType.instance())
這是多線程環境測試的代碼結果如下:
可以看到在多線程的環境中,我們產生了兩個2個對象,這就違背了我們的單例原則了,那怎么解決它呢,答案是加鎖。
import threading import time class SingleType: _instance = None _lock = threading.Lock() def __init__(self): time.sleep(1) super().__init__() @classmethod def instance(cls): if not cls._instance: with SingleType._lock: if not cls._instance: cls._instance = SingleType() return cls._instance def task(): obj = SingleType.instance() print(obj) for i in range(10): t = threading.Thread(target=task).start()
加鎖后我們看運行結果:
這樣就成功解決在多線程環境下單例的問題了, 至於問什么在最外層還要加一個if判斷,是為了提高程序的性能。
二、裝飾器模式實現
這種實現方式在我上一片將裝飾器一文中是有提及的,在這我們再實現一次
def decorator(cls): def wrap(*args, **kwargs): if not cls._instance: cls._instance = cls() return cls._instance return wrap @decorator class SingleType: _instance = None def __init__(self): super().__init__() for i in range(10): obj = SingleType() print(obj)
當然這里也會出現多線程創建多個實例的問題,可以參照上一個例子自行去實現多線程的版本。
三、使用__new__方法實現
在說__new__方法實現之前,我們需要有個前提知識,我們對象在實例話時會調用__init__方法,但是在調用__init__方法之前,類會先調用__new__方法,__new__方法中決定實例化怎樣的對象(可以是調用object的__new__方法正常接着實例話,可以生成其他類的實例等)具體知識可以自行去補充,本文主要講單例模式。
class SingleType: _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super().__new__(cls) return cls._instance def __init__(self): super().__init__() for i in range(10): obj = SingleType() print(obj)
查看運行結果:
四、使用元類實現單例模式
前提知識: 實例化對象時方法調用順序是: type類的__cal__方法,其中會調用__new__方法和__init__方法
import threading class SingleType(type): lock = threading.Lock() def __call__(cls, *args, **kwargs): if not cls._instance: with SingleType.lock: if not cls._instance: cls._instance = super().__call__(*args, **kwargs) # 這個地方也可以替換成 obj = cls.__new__(cls) --> cls.__init__(onj) --> cls._instance=obj return cls._instance class Test(metaclass=SingleType): _instance = None for i in range(10): obj = Test() print(obj)
在代碼注釋部分的作用相當於自己實現type.__call__的功能。
至此python實現單例設計模式所有的實現方式已經講完,既然說的是設計模式就要把設計模式的八大准則貼出來,時常警醒自己。