pythonで良い感じのシングルトンを書く

pythonでシングルトンを書くと結構スマートに書けるっぽいことを知ったのでメモ。 まあ、私は滅多にシングルトン使わないんですけれど。

シングルトンっていうのは以下のようなやつです。

class NormalSingleton:
    _instance = None

    def __init__(self):
        print('init')

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()

        return cls._instance


if __name__ == '__main__':
    a = NormalSingleton.get_instance()  # initが表示される
    b = NormalSingleton.get_instance()  # 何も表示されない
    print(a is b)  # True

    c = NormalSingleton()  # init
    b = NormalSingleton()  # init
    print(a is b)  # False

こいつはよくある普通の実装。 get_instanceメソッドを使ってインスタンスを取得しているうちは良いのですが、普通にインスタンス化出来てしまうのでシングルトンになりません。 他の言語だとコンストラクタをプライベートにするとかで対処するわけですが、pythonではそうも行かず。

で、どうするかっていうと、__new__ってメソッドを定義することにします。 __new__っていうのはクラスのインスタンスを作るときに呼ばれる特殊なメソッドで、__init__の前に呼ばれるようになっています。 こいつを使うとめっちゃ良い感じになる。

class SimpleSingleton:
    _instance = None

    def __init__(self):
        print('init')

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)

        return cls._instance


if __name__ == '__main__':
    a = SimpleSingleton()  # initが表示される
    b = SimpleSingleton()  # こっちもinitが表示される
    print(a is b)  # True

使い方がとてもシンプルになりました。シングルトンかそうでないかを利用者が気にする必要性が無くなってハッピー。

__init__が二回呼ばれるようになることだけ注意です。 初期化は__new__の中でやっても良いかもね?

ちなみにどちらの実装もスレッドセーフではないので、スレッドセーフにしたい場合はLockを導入しましょう。 導入すると以下のような感じ。

import threading


class ThreadingSingleton:
    _instance = None
    _lock = threading.Lock()

    def __init__(self):
        print('init')

    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)

        return cls._instance

使い方は全く変わらないので省略。 めっちゃ単純で良い感じですね。