見出し画像

Pythonでデザインパターンを学ぼう[Singleton]

Pythonを用いてのSingletonパターンの実装方法について解説します。Singletonパターンは、「生成に関するパターン」に分類されるデザインパターンです。

Singletonパターンとは?

恐らくデザインパターンで一番説明が簡単なもので、アプリケーション全体でインスタンスを一つしか生成されないように保証する仕組みのことです。

とあるクラスがあるとして、そのクラスがインスタンスを一つしか生成されないようにする仕組みがSingletonパターンです。

シンプルな実装

実装自体は非常にシンプルで簡単なものです。_unique_instanceというクラス変数を持ち、get_instance()メソッドでインスタンスを生成します。

最初の条件分岐でクラス変数_unique_instanceがNone、つまりは_unique_instanceにオブジェクトが無いときのみ、「cls._unique_instance = cls()」の部分でインスタンスの生成を行います。

その後はreturn文でクラス変数の_unique_instanceを返しています。

# -*- coding: utf-8 -*-

def main():
    s1 = Singleton.get_instance()
    print(s1)

    s2 = Singleton.get_instance()
    print(s2)
    print(s1 == s2)


class Singleton:
    _unique_instance = None

    @classmethod
    def get_instance(cls)->object:
        if not cls._unique_instance:
            cls._unique_instance = cls()

        return cls._unique_instance


if __name__ == "__main__":
    main()

main処理の部分を見ればわかりますが、Singleton.get_instance()を二回呼び出しています。

def main():
    s1 = Singleton.get_instance()
    print(s1)

    s2 = Singleton.get_instance()
    print(s2)
    print(s1 == s2)

上記コードの標準出力の結果は以下のようになります。s1 == s2の比較を行っていますが、どちらも同じオブジェクトであることがわかります。

<__main__.Singleton object at 0x7f87951bcb00>
<__main__.Singleton object at 0x7f87951bcb00>
True

通常の方法でインスタンスを生成できないようにする

先程のコードですが、少し問題があります。以下のようにget_instance()メソッド以外のコンストラクタ経由でインスタンスを生成できてしまいます。

def main():
    s1 = Singleton.get_instance()
    print(s1)

    s2 = Singleton.get_instance()
    print(s2)
    print(s1 == s2)

    # 通常のコンストラクタ経由でインスタンスを生成
    s = Singleton()
    print(s)

出力結果を見ればわかるとおり、違うオブジェクトが生成されていることがわかります。

<__main__.Singleton object at 0x7f87951bcb00>
<__main__.Singleton object at 0x7f87951bcb00>
True
<__main__.Singleton object at 0x7f1f26a1f400>

そこで通常のコンストラクタ経由でのインスタンスの生成をできないようにしてみます。以下のコードのように、__init__()で、Notimplementederror例外を起こさせます。

class Singleton:
    _unique_instance = None

    def __init__(self):
        raise NotImplementedError('not allowed')

    @classmethod
    def get_instance(cls)->object:
        if not cls._unique_instance:
            cls._unique_instance = cls()

        return cls._unique_instance

これでインスタンスを生成しようとすると、以下のようにエラーが表示されます。

Traceback (most recent call last):
  File "singleton_04.py", line 47, in <module>
    main()
  File "singleton_04.py", line 13, in main
    s = Singleton()
  File "singleton_04.py", line 31, in __init__
    raise NotImplementedError('not allowed')
NotImplementedError: not allowed

しかし、これではget_instanceメソッド経由でもインスタンスを生成できなくなってしまいます。

Traceback (most recent call last):
  File "singleton_04.py", line 33, in <module>
    main()
  File "singleton_04.py", line 6, in main
    s1 = Singleton.get_instance()
  File "singleton_04.py", line 27, in get_instance
    cls._unique_instance = cls()
  File "singleton_04.py", line 22, in __init__
    raise NotImplementedError('not allowed')
NotImplementedError: not allowed

そこで、__init__ではなく、__new__を書き換えます。Pythonでは、__init__の前に、まず__new__が呼ばれます。以下は__new__を書き換えたコードです。

class Singleton:

    _unique_instance = None

    def __new__(cls):
        raise NotImplementedError('Cannot initialize via Constructor')

    @classmethod
    def __internal_new__(cls):
        return super().__new__(cls)

    @classmethod
    def get_instance(cls):
        if not cls._unique_instance:
            cls._unique_instance = cls.__internal_new__()

        return cls._unique_instance

__internal_new__というクラスメソッドを新たに作成しました。インスタンスが既に生成されているかをget_instanceで確認し、生成されてなければ__internal_new__メソッドを呼び出しています。

これにより、通常のコンストラクタ経由での生成だと例外は発生しますが、get_instanceメソッド経由であれば、例外が発生せずにインスタンスの生成が行えています。

def main():
    s1 = Singleton.get_instance()
    print(s1)
    s2 = Singleton.get_instance()
    print(s2)

    s = Singleton()
    print(s)

上記でget_instaceメソッド経由と通常のコンストラクタ経由でインスタンスの生成を行おうとした際の実行結果です。

<__main__.Singleton object at 0x7fb08fdd1a90>
<__main__.Singleton object at 0x7fb08fdd1a90>
True
Traceback (most recent call last):
  File "singleton_04.py", line 39, in <module>
    main()
  File "singleton_04.py", line 12, in main
    s = Singleton()
  File "singleton_04.py", line 24, in __new__
    raise NotImplementedError('Cannot initialize via Constructor')
NotImplementedError: Cannot initialize via Constructor

参考資料
pythonによるデザインパターン[Singleton]
Pythonでシングルトン(Singleton)を実装してみる
Singleton パターンの使いどころをまとめてみた

この記事が気に入ったらサポートをしてみませんか?