GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Singleton(シングルトン)
Singletonパターン(シングルトン・パターン)とは、オブジェクト指向のコンピュータプログラムにおける、デザインパターンの1つである。GoF (Gang of Four; 4人のギャングたち) によって定義された。Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。ロケールやルック・アンド・フィールなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される
(以上、ウィキペディア(Wikipedia)より引用)
■ "Singleton"のサンプルプログラム
インスタンスが一つしか作成できないようなクラスを定義してみます。
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を書いてみる。
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とか、コードリーディングしているとよく見かけますね。