2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

pythonでシングルトンを__new__使わず、かつ__init__呼び出しを防いで実現する方法

Last updated at Posted at 2021-05-18

シングルトンとは

複数のインスタンスを作ることを禁止したクラスのこと。
例えば「スケジュール帳」が2個も3個もあると、スケジュールを管理するのは返って大変になる。
このような場合に「スケジュール帳のクラスをシングルトンにする」ことで、「スケジュール帳は1個しかない!シンプル!」を実現できる。

javaなどの言語で実現するシングルトン

  1. コンストラクタをprivate化し、外から勝手に呼べなくする。
  2. staticな(つまりクラスの)private変数myinstanceNULLとしておく
  3. get_instanceメソッドなどを作る。
  • 最初に呼ばれた場合、そのままprivateなコンストラクタに引数を送りつけ、インスタンスを作って、これをmyinstanceに代入の上、myinstanceを返却する
  • 2回目以降に呼ばれた場合、単にmyinstanceを返却する
  • 最初に呼ばれたのか、それとも2回目以降に呼ばれたのかは、myinstanceNULLかどうかにより判断できる

要は、コンストラクタを隠蔽することによって、初期化に関する主導権を外部から奪い、外部には「唯一のインスタンス」だけを渡す。

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継承型のオブジェクトを複数生成することはできません

参考

2
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?