如何用 Python 實現(xiàn)單例模式

單例模式是一種常用的設(shè)計模式琼稻,旨在確保一個類只有一個實例粱腻,并為應(yīng)用程序提供一個全局訪問點。Python 語言中實現(xiàn)單例模式的方法有很多售睹,每種方法都有其獨特的優(yōu)缺點和適用場景。以下將逐步介紹幾種常見的單例模式實現(xiàn)方式惭笑,并且詳細(xì)拆解每種變體的代碼和應(yīng)用場景侣姆。

1. 使用模塊級變量實現(xiàn)單例模式

在 Python 中,模塊本身就是單例的沉噩,因為當(dāng)模塊被導(dǎo)入時捺宗,Python 會將其緩存,并且同一模塊不會被重新導(dǎo)入多次川蒙⊙晾鳎基于這一特性,我們可以直接通過模塊級變量來實現(xiàn)單例模式畜眨。

# singleton_module.py

class Singleton:
    def __init__(self):
        self.value = "This is a singleton instance."

singleton_instance = Singleton()

# main.py
from singleton_module import singleton_instance

print(singleton_instance.value)

在這個實現(xiàn)中昼牛,singleton_instance 是一個全局的模塊級實例,無論在哪個模塊中導(dǎo)入 singleton_module康聂,singleton_instance 都會保持唯一性贰健。這種方式簡潔而有效,適合于簡單場景恬汁。

2. 使用類變量來實現(xiàn)單例模式

可以使用類變量來實現(xiàn)單例模式伶椿,通過將實例保存在類變量中確保類的實例只能被創(chuàng)建一次。這種方式利用了 Python 類變量的特點。

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

    def __init__(self, value):
        self.value = value

# 驗證是否為單例
singleton1 = Singleton("first instance")
singleton2 = Singleton("second instance")

print(singleton1 is singleton2)  # True
print(singleton1.value)          # "second instance"


在這里脊另,__new__ 方法用于控制對象的創(chuàng)建导狡。如果 _instanceNone,則創(chuàng)建新對象并將其存儲在 _instance 中偎痛。在之后的每次調(diào)用中都會返回已經(jīng)存在的 _instance旱捧。

3. 使用裝飾器實現(xiàn)單例模式

裝飾器是一種優(yōu)雅的 Python 語法,可以用來包裝函數(shù)或者類踩麦。我們可以定義一個裝飾器來為類提供單例模式的特性枚赡。

def singleton(cls):
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

@singleton
class Singleton:
    def __init__(self, value):
        self.value = value

# 驗證是否為單例
singleton1 = Singleton("first instance")
singleton2 = Singleton("second instance")

print(singleton1 is singleton2)  # True
print(singleton1.value)          # "first instance"

這個裝飾器實現(xiàn)了對 Singleton 類的包裝,并且確保 Singleton 只能創(chuàng)建一個實例靖榕。instances 字典用來存儲每個被裝飾類的實例标锄,只有在實例不存在時才創(chuàng)建新的實例。

4. 使用元類實現(xiàn)單例模式

元類是一種更為高級的實現(xiàn)單例模式的方式茁计。在 Python 中料皇,元類控制類的創(chuàng)建過程,因此可以通過元類實現(xiàn)對實例創(chuàng)建的控制星压。


class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            instance = super(SingletonMeta, cls).__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]

class Singleton(metaclass=SingletonMeta):
    def __init__(self, value):
        self.value = value

# 驗證是否為單例
singleton1 = Singleton("first instance")
singleton2 = Singleton("second instance")

print(singleton1 is singleton2)  # True
print(singleton1.value)          # "first instance"

在這個實現(xiàn)中践剂,SingletonMetaSingleton 類的元類。通過重載 __call__ 方法娜膘,可以控制實例的創(chuàng)建過程逊脯,并確保只有一個實例存在。這種方式的靈活性很高竣贪,適用于需要更精細(xì)控制類行為的場景军洼。

5. 使用 threading.Lock 來實現(xiàn)線程安全的單例模式

在多線程環(huán)境中,需要確保單例模式的實現(xiàn)是線程安全的演怎∝罢可以使用 threading.Lock 來實現(xiàn)這一點,從而防止多個線程同時創(chuàng)建實例爷耀。

import threading

class Singleton:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            with cls._lock:
                if not cls._instance:
                    cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

    def __init__(self, value):
        self.value = value

# 驗證是否為單例
singleton1 = Singleton("first instance")
singleton2 = Singleton("second instance")

print(singleton1 is singleton2)  # True
print(singleton1.value)          # "first instance"

這里使用了雙重檢查鎖定的機(jī)制甘桑。_lock 確保了線程在進(jìn)入創(chuàng)建實例的代碼塊時互斥,防止多個線程同時創(chuàng)建不同的實例歹叮。雙重檢查則減少了加鎖帶來的性能損耗跑杭,只有在 _instanceNone 的情況下才會加鎖創(chuàng)建實例。

6. 使用 __dict__ 屬性共享來實現(xiàn)偽單例

在一些情況下咆耿,我們可能需要多個實例德谅,但它們共享相同的數(shù)據(jù)。這種情況下可以通過 __dict__ 屬性來實現(xiàn)偽單例萨螺。

class Borg:
    _shared_state = {}

    def __init__(self):
        self.__dict__ = self._shared_state

class Singleton(Borg):
    def __init__(self, value):
        super().__init__()
        self.value = value

# 驗證是否為偽單例
singleton1 = Singleton("first instance")
singleton2 = Singleton("second instance")

print(singleton1 is singleton2)  # False
print(singleton1.value)          # "second instance"

在這個實現(xiàn)中窄做,Borg 類的所有實例共享同一個 __dict__ 屬性宅荤,因此它們的狀態(tài)是共享的。這種模式被稱為 Borg 模式浸策,與傳統(tǒng)的單例模式不同的是,它允許多個實例惹盼,但這些實例共享同樣的狀態(tài)庸汗。

7. 使用 importlib 實現(xiàn)懶加載的單例模式

有時候單例模式的實例可能比較占用資源,只有在確實需要時才創(chuàng)建實例是一種更高效的方法手报◎遣眨可以使用 importlib 模塊來實現(xiàn)懶加載的單例模式。

import importlib

class Singleton:
    def __init__(self, value):
        self.value = value

singleton_instance = None

def get_singleton_instance(value=None):
    global singleton_instance
    if singleton_instance is None:
        module = importlib.import_module(__name__)
        singleton_instance = getattr(module, 'Singleton')(value)
    return singleton_instance

# 驗證是否為單例
singleton1 = get_singleton_instance("first instance")
singleton2 = get_singleton_instance("second instance")

print(singleton1 is singleton2)  # True
print(singleton1.value)          # "first instance"

這個實現(xiàn)中掩蛤,只有在調(diào)用 get_singleton_instance 函數(shù)時才會實際創(chuàng)建 Singleton 的實例枉昏。importlib 模塊允許動態(tài)地導(dǎo)入模塊,并獲取其中的類和函數(shù)揍鸟,從而實現(xiàn)懶加載兄裂。這種方式適合那些資源消耗較大的單例對象,只有在需要時才去初始化它們阳藻。

8. 使用 WeakValueDictionary 防止內(nèi)存泄漏

在某些應(yīng)用中晰奖,我們需要實現(xiàn)單例的行為,但又不希望對象被持久化引用腥泥,導(dǎo)致內(nèi)存泄漏匾南。可以使用 weakref.WeakValueDictionary 來實現(xiàn)蛔外。

import weakref

class Singleton:
    _instances = weakref.WeakValueDictionary()

    def __new__(cls, *args, **kwargs):
        if cls not in cls._instances:
            instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]

    def __init__(self, value):
        self.value = value

# 驗證是否為單例
singleton1 = Singleton("first instance")
singleton2 = Singleton("second instance")

print(singleton1 is singleton2)  # True
print(singleton1.value)          # "first instance"

WeakValueDictionary 允許對對象的弱引用蛆楞,當(dāng)對象沒有其他強(qiáng)引用時會被自動垃圾回收。這樣可以確保單例對象在沒有其他引用時被自動銷毀夹厌,防止內(nèi)存泄漏豹爹。

9. 使用基于 dataclass 的單例實現(xiàn)

在 Python 3.7+ 中引入了 dataclass,可以使用 dataclass 的方式實現(xiàn)單例模式尊流。

from dataclasses import dataclass

@dataclass
class Singleton:
    value: str

    _instance = None

    @classmethod
    def get_instance(cls, value=None):
        if cls._instance is None:
            cls._instance = cls(value)
        return cls._instance

# 驗證是否為單例
singleton1 = Singleton.get_instance("first instance")
singleton2 = Singleton.get_instance("second instance")

print(singleton1 is singleton2)  # True
print(singleton1.value)          # "first instance"

dataclass 簡化了類的定義帅戒,自動生成了 __init__ 方法等。這種實現(xiàn)方式保持了 dataclass 的簡潔性崖技,同時通過類方法 get_instance 來控制實例的創(chuàng)建逻住。

10. 使用 functools.lru_cache 實現(xiàn)單例

Python 中的 functools.lru_cache 裝飾器也可以用于實現(xiàn)單例模式,因為它可以緩存函數(shù)的返回值迎献,保證函數(shù)在相同輸入下只會執(zhí)行一次瞎访。

from functools import lru_cache

@lru_cache(maxsize=None)
def get_singleton(value):
    class Singleton:
        def __init__(self, value):
            self.value = value

    return Singleton(value)

# 驗證是否為單例
singleton1 = get_singleton("first instance")
singleton2 = get_singleton("second instance")

print(singleton1 is singleton2)  # True
print(singleton1.value)          # "first instance"

通過 lru_cache 實現(xiàn)了緩存功能,maxsize=None 意味著沒有緩存大小的限制吁恍,只要輸入的參數(shù)一致扒秸,返回的實例就是唯一的播演。這種方式適合那些函數(shù)式編程場景下的單例實現(xiàn)。

總結(jié)

以上介紹了在 Python 中實現(xiàn)單例模式的多種方法伴奥,每種方法都有其適用的場景和優(yōu)缺點:

  • 模塊級變量實現(xiàn)適用于最簡單的場景写烤,代碼易于理解且無需額外的同步控制。
  • 使用類變量實現(xiàn)是一種經(jīng)典的單例實現(xiàn)方式拾徙,適合于控制類實例化的場景洲炊。
  • 使用裝飾器是一種非常優(yōu)雅的方式,適用于需要給多類增加單例特性的場景尼啡。
  • 使用元類可以精細(xì)控制類的行為暂衡,是一種比較高級的實現(xiàn)方式,適用于對類的創(chuàng)建過程有更多需求的場合崖瞭。
  • 使用線程鎖來確保線程安全適用于多線程環(huán)境狂巢,確保單例實例不會被重復(fù)創(chuàng)建。
  • Borg 模式適用于需要共享狀態(tài)但允許創(chuàng)建多個實例的場景书聚,保持了類的靈活性唧领。
  • 懶加載單例適用于那些創(chuàng)建成本較高,只有在確實需要時才去創(chuàng)建的場景雌续。
  • 使用 WeakValueDictionary 可以有效防止內(nèi)存泄漏疹吃,適用于短生命周期的單例對象。
  • 基于 dataclass 的實現(xiàn)保留了代碼的簡潔性西雀,同時實現(xiàn)了單例的特性萨驶。
  • 使用 lru_cache 實現(xiàn)單例適用于函數(shù)式編程風(fēng)格的應(yīng)用場景,簡潔且高效艇肴。

每種方法都有其優(yōu)缺點和適用場景腔呜,根據(jù)項目的具體需求和代碼風(fēng)格選擇合適的實現(xiàn)方式,可以讓代碼更加簡潔再悼、高效和易維護(hù)核畴。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市冲九,隨后出現(xiàn)的幾起案子谤草,更是在濱河造成了極大的恐慌,老刑警劉巖莺奸,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丑孩,死亡現(xiàn)場離奇詭異,居然都是意外死亡灭贷,警方通過查閱死者的電腦和手機(jī)温学,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甚疟,“玉大人仗岖,你說我怎么就攤上這事逃延。” “怎么了轧拄?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵揽祥,是天一觀的道長。 經(jīng)常有香客問我檩电,道長盔然,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任是嗜,我火速辦了婚禮,結(jié)果婚禮上挺尾,老公的妹妹穿的比我還像新娘鹅搪。我一直安慰自己,他們只是感情好遭铺,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布丽柿。 她就那樣靜靜地躺著,像睡著了一般魂挂。 火紅的嫁衣襯著肌膚如雪甫题。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天涂召,我揣著相機(jī)與錄音坠非,去河邊找鬼。 笑死果正,一個胖子當(dāng)著我的面吹牛炎码,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秋泳,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼潦闲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了迫皱?” 一聲冷哼從身側(cè)響起歉闰,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卓起,沒想到半個月后和敬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡戏阅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年概龄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饲握。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡私杜,死狀恐怖蚕键,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衰粹,我是刑警寧澤锣光,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站铝耻,受9級特大地震影響誊爹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓢捉,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一频丘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泡态,春花似錦搂漠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至靶壮,卻和暖如春怔毛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腾降。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工拣度, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人螃壤。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓蜡娶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親映穗。 傳聞我的和親對象是個殘疾皇子窖张,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容