1. ホーム
  2. python

[解決済み] Pythonでシングルトンを作成する

2022-03-17 10:11:37

質問

の可否を議論するための質問ではありません。 シングルトンデザインパターン しかし、このパターンをPythonで実装する場合、どのように実装するのが最もPythonicであるかを議論したいのです。この例では、私は「最もpythonic」を「最小驚嘆の原則」に従うと定義しています。 .

私はシングルトンになる複数のクラスを持っています(私のユースケースはロガーですが、これは重要ではありません)。私は、単に継承するか装飾することができるときに、追加のガムテープでいくつかのクラスを散らかしたくありません。

ベストな方法


方法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

Pros

  • デコレータは、多重継承よりも直感的な方法で追加することができます。

短所

  • を使用して作成されたオブジェクトは MyClass() は真のシングルトン・オブジェクトになる。 MyClass 自体はクラスではなく関数なので、そこからクラスメソッドを呼び出すことはできません。また

    x = MyClass();
    y = MyClass();
    t = type(n)();
    
    

では x == y しかし x != t && y != t


方法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

Pros

  • 真のクラスである

短所

  • 多重継承 - eugh! __new__ は、2つ目のベースクラスからの継承の際に上書きされる可能性があるのでは?必要以上に考えなければならない。

方法3:A メタクラス

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

Pros

  • 真のクラスである
  • オートマティックに継承をカバーする
  • 使用方法 __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

Pros

  • 真のクラスである
  • オートマティックに継承をカバーする

短所

  • 新しいクラスを作成するたびにオーバーヘッドが発生しないか?ここでは、シングルトンにしたいクラスごとに2つのクラスを作成しています。私の場合はこれで問題ないのですが、これがスケールしないのではないかと心配になります。もちろん、このパターンをスケールさせるのが簡単すぎるかどうかという議論もありますが...。
  • のポイントは何でしょうか? _sealed 属性
  • を使用して、ベース・クラスの同名のメソッドを呼び出すことはできません。 super() というのも、再帰的に検索されるからです。つまり __new__ まで呼び出す必要があるようなクラスのサブクラスはできません。 __init__ .

方法5:モジュール

モジュールファイル singleton.py

Pros

  • 複雑なものよりシンプルなものが良い

短所

  • 遅延インスタンス化されない

解決方法は?

メタクラスを使用する

私がお勧めするのは 方法その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__ が呼び出されるたびに

        else:
            cls._instances[cls].__init__(*args, **kwargs)

から if の文は Singleton.__call__ .

メタクラスについて少し説明します。メタクラスとは クラスの つまり、クラスは そのメタクラスのインスタンス . Pythonでオブジェクトのメタクラスを見つけるには、次のようにします。 type(obj) . 通常の新スタイルのクラスは type . Logger は、上記のコードでは class 'your_module.Singleton' の(唯一の)インスタンスと同じように Logger の型になります。 class 'your_module.Logger' . でロガーを呼び出すと Logger() のメタクラスを問い合わせます。 Logger , Singleton を実行することで、インスタンスの生成を先取りすることができます。この処理は、Pythonがクラスに何をすべきか尋ねるために __getattr__ でその属性を参照したとき、その属性は myclass.attribute .

メタクラスは、基本的に クラスの定義が意味するもの とその定義をどのように実装するかということです。例えば http://code.activestate.com/recipes/498149/ これは、本質的にC言語スタイルの struct をメタクラスを使ってPythonで表現しています。スレッド メタクラスの(具体的な)使用例にはどのようなものがありますか? 一般的には、宣言的なプログラミング、特にORMで使われるようなものに関連しているようです。

このような状況で、もしあなたが自分の メソッド#2 を定義し、サブクラスが __new__ メソッドを使用する場合、それは 毎回実行される を呼び出すと SubClassOfSingleton() -- これは、ストアドインスタンスを返すメソッドを呼び出す役割を担っているからです。メタクラスでは 一度だけ呼ばれる 唯一のインスタンスが作成されたときです。あなたが望むのは クラスを呼び出す意味をカスタマイズする これは型によって決定されます。

一般的には 意味をなす メタクラスを使ってシングルトンを実装することです。シングルトンは特別なもので、それは 一度だけ作成される をカスタマイズするのがメタクラスです。 クラスの作成 . メタクラスを使用すると、次のようなことが可能になります。 より多くの制御 というのは、他の方法でシングルトン・クラスの定義をカスタマイズする必要がある場合です。

あなたのシングルトン は多重継承を必要としません。 (メタクラスは基底クラスではないので)しかし 作成されたクラスのサブクラス 多重継承を使用する場合は、シングルトン・クラスが 最初 / 最左 を再定義するメタクラスを持つものである。 __call__ これは問題になる可能性は非常に低いです。インスタンスディクトは インスタンスの名前空間でない ので、誤って上書きすることはないでしょう。

また、シングルトンパターンは単一責任原則(Single Responsibility Principle)に反しているという話も聞きます。 たった一つのこと . そうすれば、別のものを変更する必要がある場合でも、コードが台無しになることを心配する必要がなくなります。メタクラスの実装 このテストに合格 . このメタクラスは パターンを実行する である必要はなく、作成されたクラスとサブクラスは シングルトンであることを意識する . 方法その1 で指摘したように、MyClass 自体はクラスではなく関数なので、そこからクラスメソッドを呼び出すことはできません。

Python2,3対応版

Python2と3の両方で動作するものを書くには、少し複雑なスキームを使用する必要があります。メタクラスは通常 type を使えば、実行時に中間的な基底クラスをメタクラスとして動的に作成し、それを その のベースクラスとして使用します。 Singleton の基底クラスです。次に示すように、説明するのはやるより難しいです。

# works in Python 2 & 3
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _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 Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass

この方法の皮肉な点は、メタクラスを実装するためにサブクラス化を利用していることです。考えられる利点としては、純粋なメタクラスとは異なり isinstance(inst, Singleton) が返されます。 True .

訂正履歴

別の話題ですが、すでにお気づきかもしれませんが、元の投稿にあるベースクラスの実装は間違っています。 _instances にする必要があります。 クラスで参照される を使用する必要があります。 super() でないと 再帰 で、そして __new__ は、実際には静的メソッドであり にクラスを渡します。 はクラスメソッドではなく、実際のクラスは は作成されていません。 が呼び出されたとき、まだ これらのことは、メタクラスの実装でも同じように当てはまります。

class Singleton(object):
  _instances = {}
  def __new__(class_, *args, **kwargs):
    if class_ not in class_._instances:
        class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
    return class_._instances[class_]

class MyClass(Singleton):
  pass

c = MyClass()

クラスを返すデコレータ

もともとコメントを書いていたのですが、長くなりすぎたので、ここに追記します。 方法その4 は他のデコレーターバージョンよりも優れていますが、シングルトンに必要なコードよりも多く、何をするものなのかが明確ではありません。

主な問題は、クラスがそれ自身のベースクラスであることに起因しています。まず、あるクラスが、ほぼ同じ名前の、そのクラスの __class__ 属性はどうでしょうか?また、このことは ベースクラスの同名のメソッドを呼び出すメソッドは、すべて super() というのも、これらは再帰的に処理されるからです。つまり、あなたのクラスでは __new__ を必要とするクラスから派生させることはできません。 __init__ が呼び出されます。

シングルトンパターンを使用する場合

あなたのユースケースは 良い例の一つ シングルトンを使いたいということです。あなたはコメントの中で "私にとってロギングは常にシングルトンの自然な候補に見えましたと述べています。 全くその通り .

シングルが悪いと言われるとき、最も多いのは、その理由が 暗黙の共有状態 . 一方、グローバル変数やトップレベルモジュールのインポートは 明示的 は状態を共有し、それ以外の渡されるオブジェクトは一般にインスタンス化されます。これは良い点です。 ただし、次の2つの例外があります。 .

1つ目は、様々な場所で言及されることですが、シングルトーンの場合です。 定数 . グローバル定数、特に列挙型の使用は広く受け入れられており、何があっても大丈夫なので、正気だと考えられています。 どのユーザーも、他のユーザーのためにそれを台無しにすることはできません。 . これは、定数のシングルトンにも同様に当てはまります。

2つ目の例外は、あまり言及されませんが、その逆で、シングルトンが 単なるデータシンク 直接または間接的に)データソースではありません。これが、ロガーがシングルトンにとって「自然な使い方」であると感じる理由です。様々なユーザーが ロガーを変更しない 他のユーザが気になるような方法で 共有状態ではない . このことは、シングルトンパターンに対する主要な議論を否定し、シングルトンパターンを合理的な選択とするのは、その 使いやすさ というタスクがあります。

以下は、引用です。 http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :

さて、シングルトンには1種類だけ、問題ないものがあります。それは、到達可能なオブジェクトがすべてイミュータブル(不変)であるシングルトンです。すべてのオブジェクトがイミュータブルであれば、シングルトンはグローバルな状態を持ちません。しかし、この種のシングルトンをミュータブルなものに変えるのはとても簡単で、とても滑りやすい坂道です。だから、私はこの種のシングルトンには反対です。enumerationにstateを入れなければ大丈夫なので、ご遠慮ください)

他の種類のシングルトンは、コードの実行に影響を与えないもので、副作用がありません。ロギングがその典型例です。これはシングルトンやグローバルなステートを含んでいます。あるロガーが有効であるかどうかに関わらず、アプリケーションの動作は変わらないので、許容できます(あなたを傷つけないという意味で)。ここでの情報の流れは一方通行です。アプリケーションからロガーへです。ロガーからアプリケーションに情報が流れないので、ロガーがグローバル状態であっても、 ロガーは許容されます。しかし、一般に、ロガーは状態によって変化しますが、有害ではありません。