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 パターンの使いどころをまとめてみた
この記事が気に入ったらサポートをしてみませんか?