シングルトンとは
複数のインスタンスを作ることを禁止したクラスのこと。
例えば「スケジュール帳」が2個も3個もあると、スケジュールを管理するのは返って大変になる。
このような場合に「スケジュール帳のクラスをシングルトンにする」ことで、「スケジュール帳は1個しかない!シンプル!」を実現できる。
javaなどの言語で実現するシングルトン
- コンストラクタを
private
化し、外から勝手に呼べなくする。 -
static
な(つまりクラスの)private
変数myinstance
をNULL
としておく -
get_instance
メソッドなどを作る。
- 最初に呼ばれた場合、そのまま
private
なコンストラクタに引数を送りつけ、インスタンスを作って、これをmyinstance
に代入の上、myinstance
を返却する - 2回目以降に呼ばれた場合、単に
myinstance
を返却する - 最初に呼ばれたのか、それとも2回目以降に呼ばれたのかは、
myinstance
がNULL
かどうかにより判断できる
要は、コンストラクタを隠蔽することによって、初期化に関する主導権を外部から奪い、外部には「唯一のインスタンス」だけを渡す。
pythonでのシングルトンは何故難しいか
しかし、pythonにおけるオブジェクト指向では、アクセス制御ができない。
疑似private
なプロパティを作る方法は存在するが、コンストラクタ__init__
では疑似private
にすることは不可能。
故に、__init__
より前に動く__new__
をいじるなどするか、
あるいは「コンストラクタ、呼べるけど呼んじゃダメだからね!」と、外部に大人の都合を押し付けることになる。
この場合、「子供」(大人の都合を押し付けるべきでない相手)に対してシングルトンを提供できない。
(オブジェクト指向のメリットの重要な一つは、「外部の利用者が内部仕様を知らなくてよい」ことであり、つまり「子供」でも利用出来ることだ。)
__new__
を使わずに頑張る方法
__new__
を使えるプログラマは別に困らないかもしれないが、__new__
を使えないプログラマにとって、シングルトンを作る方法が今のところない。__new__
を使わずに、「private
な__init__
」を実現できればうれしい。
private
な__init__
が実現できてほしい理由は、「外部から呼ばれないような__init__
が作りたい」からである。
であれば別に「外部の利用者が知らないような引数を受け取った時だけ動作する__init__
」を作れば、それでいいじゃないか。
つまり
class シングルトン:
def __init__(self, *args, **kwargs):
if "秘密のキーワード" in kwargs and kwargs["秘密のキーワード"] == "秘密の値":
print("おぉ!秘密のキーワードと秘密の値を知っているってことは、"+
"今俺はシングルトンクラス内部から呼ばれたということだね!")
print("だったら、外部に見られたくない処理を行えるね!")
else:
raise TypeError("ごめんね!シングルトンだから外部からの初期化はできないことになってるのよー")
とすれば、「秘密のキーワード
と秘密の値
を知らない外部にとっては__init__
は無効になっていて、
かつ内部からは__init__
が問題なく利用可能」という状況が実現する。
これはもう、「private
な__init__
」と同様な能力を持っているといってよさそうだ。
例
モード = 0
# 0: もっとも新しいインスタンスを生かす場合
# 1: もっとも古いインスタンスを生かす場合
# それ以外のintオブジェクト: 別のインスタンスが作られそうな時TypeError
class Singleton:
_instance = None
@classmethod
def get_instance(cls, *args, **kwargs):
kwargs.update({"__singleton":"__singleton"})
mode = モード
# 0: もっとも新しいインスタンスを生かす場合
# 1: もっとも古いインスタンスを生かす場合
# それ以外のintオブジェクト: 別のインスタンスが作られそうな時TypeError
if mode:
if mode != 1 and cls._instance is not None:
raise TypeError("singleton継承型のオブジェクトを複数生成することはできません")
cls._instance = cls._instance or cls(*args, **kwargs)
else:
cls._instance = ("dummy" if cls._instance.__init__(*args, **kwargs) else cls._instance) or cls(*args, **kwargs)
return cls._instance
@classmethod
def assert_initialize(cls, *args, **kwargs):
if "__singleton" in kwargs.keys() and kwargs["__singleton"] == "__singleton":
del kwargs["__singleton"]
return (args, kwargs)
else:
raise type("AccessException", (Exception,), {})("private化されたコンストラクタにアクセスしないでください")
class MySingleton(Singleton):
def __init__(self, *args, **kwargs):
(args, kwargs) = type(self).assert_initialize(*args, **kwargs)
self.args = args
self.kwargs = kwargs
try:
MySingleton()
except Exception as e:
print("エラーメッセージ", e) # private化されたコンストラクタにアクセスしないでください
mysingleton1 = MySingleton.get_instance(1,2,3,abc="A")
mysingleton2 = MySingleton.get_instance(4,5,6,abc="B")
print("mysingleton1 == mysingleton2", mysingleton1 == mysingleton2)
print("mysingleton1", mysingleton1.args, mysingleton1.kwargs)
print("mysingleton2", mysingleton2.args, mysingleton2.kwargs)
モード = 0
とした場合、
次の結果を得る
エラーメッセージ private化されたコンストラクタにアクセスしないでください
mysingleton1 == mysingleton2 True
mysingleton1 (4, 5, 6) {'abc': 'B'}
mysingleton2 (4, 5, 6) {'abc': 'B'}
モード = 1
とした場合、
次の結果を得る
エラーメッセージ private化されたコンストラクタにアクセスしないでください
mysingleton1 == mysingleton2 True
mysingleton1 (1, 2, 3) {'abc': 'A'}
mysingleton2 (1, 2, 3) {'abc': 'A'}
モード = 2
とした場合、
次の結果を得る
エラーメッセージ private化されたコンストラクタにアクセスしないでください
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-50-02ff95b76478> in <module>()
44
45 mysingleton1 = MySingleton.get_instance(1,2,3,abc="A")
---> 46 mysingleton2 = MySingleton.get_instance(4,5,6,abc="B")
47
48 print("mysingleton1 == mysingleton2", mysingleton1 == mysingleton2)
<ipython-input-50-02ff95b76478> in get_instance(cls, *args, **kwargs)
17 if mode:
18 if mode != 1 and cls._instance is not None:
---> 19 raise TypeError("singleton継承型のオブジェクトを複数生成することはできません")
20 cls._instance = cls._instance or cls(*args, **kwargs)
21 else:
TypeError: singleton継承型のオブジェクトを複数生成することはできません
参考