Pythonで、デザインパターン「Singleton」を学ぶ


GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。

■ Singleton(シングルトン)

Singletonパターン(シングルトン・パターン)とは、オブジェクト指向のコンピュータプログラムにおける、デザインパターンの1つである。GoF (Gang of Four; 4人のギャングたち) によって定義された。Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。ロケールやルック・アンド・フィールなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される

(以上、ウィキペディア(Wikipedia)より引用)

■ "Singleton"のサンプルプログラム

インスタンスが一つしか作成できないようなクラスを定義してみます。

Main.py
class Singleton(object):
    def __new__(cls, *args, **kargs):
        if not hasattr(cls, "_instance"):
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

class Myclass(Singleton):
    def __init__(self, input):
        self.input = input

if __name__ == '__main__':
    one = Myclass(1)
    print("one.input={0}".format(one.input))
    two = Myclass(2)
    print("one.input={0}, two.input={1}".format(one.input, two.input))
    one.input = 0
    print("one.input={0}, two.input={1}".format(one.input, two.input))

動かしてみる

$ python Main.py 
one.input=1
one.input=2, two.input=2
one.input=0, two.input=0

なるほど、まるでグローバル変数のように振る舞いますねぇー

□ 備忘録

(1) __new____init__とは、何が違うのか?

1. __new__の場合 (以下、"Python言語リファレンス"より引用)

  • クラスclsの新しいインスタンスを作るために呼び出されます。
  • インスタンスを生成するよう要求されているクラスclsを第一引数にとります。
  • 残りの引数はオブジェクトのコンストラクタの式 (クラスの呼び出し文) に渡されます。
  • __new__()の戻り値は、新しいオブジェクトのインスタンス (通常は cls のインスタンス) でなければなりません。
  • 典型的な実装では、クラスの新たなインスタンスを生成するときには super().__new__(cls[, ...])に適切な引数を指定してスーパクラスの__new__()メソッドを呼び出し、新たに生成されたインスタンスに必要な変更を加えてから返します。
  • __new__()が、clsのインスタンスを返さない場合、インスタンスの__init__()メソッドは呼び出されません。
  • __new__()の主な目的は、変更不能な型 (int, str, tuple など) のサブクラスでインスタンス生成をカスタマイズすることにあります。また、クラス生成をカスタマイズするために、カスタムのメタクラスでよくオーバーライドされます。

インスタンスオブジェクトが生成される前に呼ばれ、第一引数clsには、クラスオブジェクトが代入されていて、オブジェクトselfをインスタンス化することを目的とするんですね。

※python2.7系と、python3系では、__new__メソッドへの引数の与え方に差異が存在するため、注意が必要です。Web記事:"How To Use Python new Method Example"

2. __init__の場合 (以下、"Python言語リファレンス"より引用)

  • インスタンスが (__new__()によって) 生成された後、それが呼び出し元に返される前に呼び出されます。
  • 引数はクラスのコンストラクタ式に渡したものです。
  • 基底クラスとその派生クラスがともに__init__()メソッドを持つ場合、派生クラスの__init__()メソッドは基底クラスの __init__()メソッドを明示的に呼び出して、インスタンスの基底クラス部分が適切に初期化されること保証しなければなりません。例えば、super().__init__([args...]) 。
  • __new__()__init__()は連携してオブジェクトを構成する(__new__()が作成し、__init__()がそれをカスタマイズする)ので、__init__()から非None値を返してはいけません; そうしてしまうと、実行時に TypeError が送出されてしまいます。

インスタンスオブジェクトが生成された後に呼ばれ、第一引数selfには、インスタンスオブジェクトが代入されていて、オブジェクトselfを初期化することを目的とするんですね。

(2) オリジナルな"Singleton"を書いてみる

__new__メソッドを使用せずに、Singletonを書いてみる。

Main.py
class Singleton(object):
    @classmethod
    def get_instance(cls, input):
        if not hasattr(cls, "_instance"):
            cls._instance = cls(input)
        else:
            cls._instance.input = input
        return cls._instance

class Myclass(Singleton):
    def __init__(self, input):
        self.input = input

if __name__ == '__main__':
    one = Myclass.get_instance(1)
    print("one.input={0}".format(one.input))
    two = Myclass.get_instance(2)
    print("one.input={0}, two.input={1}".format(one.input, two.input))
    one.input = 0
    print("one.input={0}, two.input={1}".format(one.input, two.input))

動かしてみる

$ python Main.py 
one.input=1
one.input=2, two.input=2
one.input=0, two.input=0

こっちのスタイルの方が、使い勝手がシンプルですね。
OpenStackとか、コードリーディングしているとよく見かけますね。

■ 参考URL