PythonとSingleton(単品)モード

6869 ワード

pythonにおけるSingleton modeの実装は以下の通りである.
class Foo: pass
def instance():
global inst
try:
inst
except:
inst = Foo ()
return inst

この実装の利点は、単純で直感的であるが、欠点も同様に明らかである.
  • クライアントコードは、クラスのオブジェクトを作成するためにinstance()という方法を明示的に知る必要がある.
  • は、同時環境下でこのような実現は信頼できない.

  • 2つ目はかなり深刻な欠陥で、上記のコードを使用すると、1つ以上のインスタンスが現れないことを祈るしかありません(確率は低いですが、可能です).そうしないと、奇妙な問題が発生します.
    一つの少し良い実現は以下の通りです.
    class Singleton(object):   
    objs = {}
    def __new__(cls, *args, **kv):
    if cls in cls.objs:
    return cls.objs[cls]
    cls.objs[cls] = object.__new__(cls)

    このインプリメンテーションは、Singletonクラスから継承される限り、1つのインスタンスしか必要としないクラスがSingleton modeを実装したいという最初の欠点を解決し、コードのどこでクラスをインスタンス化しても、そのクラスの1つのインスタンスしか存在しない.
    2つ目の欠点を解決するために、次のように進化したバージョンが登場しました.
    class Singleton(object):
    objs = {}
    objs_locker = threading.Lock()

    def __new__(cls, *args, **kv):
    if cls in cls.objs:
    return cls.objs[cls]

    cls.objs_locker.acquire()
    try:
    if cls in cls.objs: ## double check locking
    return cls.objs[cls]
    cls.objs[cls] = object.__new__(cls)
    finally:
    cls.objs_locker.release()

    見覚えがあるかどうか、そうだ、これはSingleton modeにおける古典的な二重検査ロックメカニズムであり、このメカニズムは同時環境下でSingleton modeの正確な実現を確保している.
    ここまで、上記の2つの欠点は進化したコードで解決され、完璧に見えますが、物語はまだ終わっていません.改善されたコードに何か問題があるのではないでしょうか.
    続行する前に、まず__について紹介します.new__および_init__の基礎知識、Pythonの経典類と新式類はすべて支持します_init__関数ですが、新しいクラスのみがサポートされています.new__を選択します.new__関数はインスタンスを作成し、次に__を呼び出します.init__関数はこのインスタンスを初期化し、これらの関数が存在しない場合はPythonがデフォルトで提供しているバージョンを呼び出しますが、ユーザーがこれらの関数の実装を提供している場合は、ユーザーが実装しているバージョンを呼び出します.
    上記の改良されたコードにも2つの問題があります.
  • ユーザーがカスタムバージョンを提供した場合new__関数、Singletonクラスに上書きまたは干渉します_new__クラスインスタンスの作成プロセスをカスタマイズするユーザーが少ないため、このような状況が発生する確率は極めて低い.
  • ユーザーがカスタムバージョンを提供した場合init__関数を使用すると、クラスがインスタンス化されるたびに、_init__呼び出されます.これは明らかに不可能です.init__インスタンスを作成するときに一度だけ呼び出されるべきです.

  • 解決のためにinit__複数回呼び出された問題です.より高度な(複雑な)バージョンは次のとおりです.
    class Singleton(object):

    objs = {}
    objs_locker = threading.Lock()

    def __new__(cls, *args, **kv):
    if cls in cls.objs:
    return cls.objs[cls]['obj']

    cls.objs_locker.acquire()
    try:
    if cls in cls.objs: ## double check locking
    return cls.objs[cls]['obj']
    obj = object.__new__(cls)
    cls.objs[cls] = {'obj': obj, 'init': False}
    setattr(cls, '__init__', cls.decorate_init(cls.__init__))
    finally:
    cls.objs_locker.release()

    @classmethod
    def decorate_init(cls, fn):
    def init_wrap(*args):
    if not cls.objs[cls]['init']:
    fn(*args)
    cls.objs[cls]['init'] = True
    return

    return init_wrap 

    ここを見ると、簡単なことをそんなに複雑にする必要があるのではないかと思うかもしれません.
    私の答えは、状況によって異なります.
  • 実行環境に同時性が存在せず、顧客コードが追加のワークロード(instance()という関数を覚えている)を気にしない場合は、本明細書の最初のコードが最適です.
  • コンカレント環境が存在しても、クライアントコードが追加のワークロード(インスタンス化関数ごとに覚えておくだけでなく、同じ場所で呼び出し、呼び出し順序を保証することも覚えておく)をあまり気にしない場合は、プログラムの起動段階ですべての単一のインスタンスを初期化するのに適しています.
  • 並行して存在し、お客様が怠け者である(怠け者はプログラマーの美徳である)場合、ビジネスに関係のないことをあまり覚えたくないし、単一のものを集中的に初期化したくないし、初期化の順序を保証しなければならない.また、プラグインのあるシステムでは集中的に初期化することは不可能である場合があります.では、最後の複雑なコードを使いましょう.

  •  
    得るものは必ず失うものがある
    簡単な実装では、コードロジックが明確で、メンテナンス量が小さく、修正も簡単ですが、アプリケーション環境が制限されており(前述の2点)、一部の初期化作業はお客様に任せられており(instance関数を呼び出す)、小さなシステムでは問題ありませんが、大きなシステムでは、これは明らかな負担になります(特にインスタンス化関数の名前が統一されていない場合).
     
    複雑な実装では、すべての作成操作が1つの場所で完了し、環境を仮定せず、お客様に負担を与えず、普通のクラスのように使用されます.最も重要なのは、単品作成(ベースクラス)とビジネスコード(継承クラス)を分離し、明確に区分することです.欠点はコードが複雑で、メンテナンスと修正が難しく、Pythonの高度な言語特性が必要であることです.
    転載先:PythonとSingleton(単品)モード