首頁 > 後端開發 > Python教學 > 在 Python 中實現單例的最佳方式是什麼?

在 Python 中實現單例的最佳方式是什麼?

Susan Sarandon
發布: 2024-12-17 16:07:09
原創
296 人瀏覽過

What is the best way to implement a singleton in Python?

Python中實現單例的最佳方式

儘管單例設計模式的優劣不是本文的討論重點,本文將探討如何在Python 中以最符合Python 風格的方式實現此模式。在這裡,"最符合 Python 風格" 的意思是遵循"最少驚訝原則"。

實作方法

方法1:裝飾器

def singleton(class_):
    instances = {}

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

    return getinstance

@singleton
class MyClass(BaseClass):
    pass
登入後複製
登入後複製
登入後複製

優點:

  • 裝飾器具有相加性,比多重繼承更直觀。

缺點:

  • 使用MyClass() 建立的物件是真正的單例對象,但MyClass 本身是一個函數,而不是一個類,因此無法呼叫類別方法。

方法 2:基類

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 MyClass(Singleton, BaseClass):
    pass
登入後複製
登入後複製

優點:

  • 它是真正的類別。

缺點:

  • 多重繼承,令人不快。從第二個基底類別繼承時,__new__ 可能會被重寫。

方法 3:元類別

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]

# Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

# Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass
登入後複製

優點:

  • 它是真正的類別。
  • 自動涵蓋繼承。
  • 正確地使用 __metaclass__ (並讓我了解了它)。

缺點:

  • 沒有缺點。

方法 4:傳回同名類別的裝飾器

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 MyClass(BaseClass):
    pass
登入後複製

優點:

  • 它是真正的類別。
  • 自動涵蓋繼承。

缺點:

  • 為每個要變成單例的類別建立兩個類別是否會有開銷?雖然這在我的案例中是可以的,但我擔心它可能無法擴展。
  • _sealed 屬性的目的是什麼?
  • 無法使用 super() 呼叫基底類別中同名的方法,因為它們會遞歸。這意味著無法自訂 __new__,也無法子類化需要呼叫 __init__ 的類別。

方法 5:模組

單例模組 singleton.py。

優點:

  • 簡單勝於複雜。

缺點:

  • 未延遲實例化。

建議方法

我建議使用 方法 2,但最好使用元類別而不是基底類別。以下是一個範例實作:

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 Logger(object):
    __metaclass__ = Singleton
登入後複製

或在Python3 中:

class Logger(metaclass=Singleton):
    pass
登入後複製

如果希望每次呼叫類別時都執行__init__,請將下列程式碼加入Singleton.__call__中的if 語句中:

def singleton(class_):
    instances = {}

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

    return getinstance

@singleton
class MyClass(BaseClass):
    pass
登入後複製
登入後複製
登入後複製

元類的作用

元類是類的類,也就是說,類是其元類的實例。可以透過 type(obj) 找到 Python 中物件的元類別。正常的新的類別是 type 類型。上面的 Logger 將屬於 class 'your_module.Singleton' 類型,就像 Logger 的(唯一)實例將屬於 class 'your_module.Logger' 類型一樣。當使用 Logger() 呼叫 logger 時,Python 首先詢問 Logger 的元類別 Singleton 應該做什麼,允許搶先進行實例建立。這個過程與 Python 透過呼叫 __getattr__ 詢問類別應該做什麼來處理其屬性類似,而你透過執行 myclass.attribute 來引用其屬性。

元類別本質上決定了呼叫類別的含義以及如何實現該含義。請參閱例如 http://code.activestate.com/recipes/498149/,它使用元類別主要在 Python 中重新建立 C 樣式結構。討論線程 [元類別的具體用例有哪些? ](https://codereview.stackexchange.com/questions/82786/what-are-some-concrete-use-cases-for-metaclasses) 也提供了一些範例,它們通常與聲明性程式設計有關,尤其是在ORM中使用。

在這種情況下,如果你使用你的方法2,並且一個子類別定義了一個__new__ 方法,它將在每次呼叫SubClassOfSingleton() 時執行,因為它負責呼叫傳回儲存的實例的方法。使用元類,它只會執行一次,即在創建唯一實例時。你需要自訂呼叫類別的定義,這是由其類型決定的。

一般來說,使用元類別來實現單例是有意義的。單例很特別,因為它的實例只創建一次,而元類別是自訂創建類別的實作方式,使其行為與普通類別不同。使用元類別可以讓你在其他方式需要自訂單例類別定義時有更多的控制權。

當然

你的單例不需要多重繼承(因為元類不是基類),但對於繼承創建類的子類,你需要確保單例類是第一個/最左邊的元類,重新定義__call__。這不太可能有問題。實例字典並不在實例的名稱空間中,因此不會意外地覆寫它。

你還會聽到單例模式違反了"單一職責原則",即每個類別都應該只做一件事。這樣,你不必擔心在需要更改另一種程式碼時破壞程式碼所做的某一件事,因為它們是獨立且封裝的。元類實作通過了此測試。元類別負責強制模式,創建的類別和子類別不需要意識到它們是單例。 方法 1 沒有通過此測試,正如你用"MyClass 本身是一個函數,而不是類,因此無法調用類方法"指出的那樣。

Python 2 和 3 相容版本

在 Python2 和 3 中編寫程式碼需要使用稍微複雜一點的方案。由於元類通常是 type 類的子類,因此可以使用元類在運行時動態建立一個帶有它作為元類的中介基類,然後使用該基類作為公共單例基類的基類。這說起來比做起來難,如下:

def singleton(class_):
    instances = {}

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

    return getinstance

@singleton
class MyClass(BaseClass):
    pass
登入後複製
登入後複製
登入後複製

這種方法的一個諷刺之處在於它使用子類化來實現元類。一個可能的優點是,與純元類別不同的是,isinstance(inst, Singleton) 將傳回 True。

修正

關於另一個主題,你可能已經注意到了,但你原始貼文中的基類實作是錯誤的。需要在類別中引用 _instances,你需要使用 super(),或是類別方法的靜態方法,因為在呼叫時實際類別尚未建立。所有這些對於元類實作也是適用的。

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 MyClass(Singleton, BaseClass):
    pass
登入後複製
登入後複製

以上是在 Python 中實現單例的最佳方式是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板