Pythonでデザインパターン 伍ノ型「Singleton」
今回はPythonの勉強しながらデザインパターンのSingletonをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Singletonとは
直訳すると「単一要素」みたいな意味になります。
wikiには「そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのこと」とあります。
Singletonパターンはwikiにも説明があるとおり、クラスのインスタンスが複数作られないようにするためのメカニズムです。
アプリケーション全体で統一しなければならない仕様を実装しなければならないときに役に立ちます。
私はデザインパターンの中でこのパターンが一番お気に入りで、オブジェクト指向言語でなにか作るときにを頻繁に利用しています。
実際の開発現場でも使っている人をチラホラ見かけたので習得しておいても損はないパターンかもしれないですね(・`ω´・)b
Singletonパターンが欲しくなるとき
それではいつものごとく、いろいろコードを書きながら考えてみます\(^ω^)/
まず以下のようなConfigクラスがあったとします。
class Config(object) : def __init__(self) : self.ParameterA = 1 self.ParameterB = 2 self.ParameterC = 3
このクラスを普通に使おうとするとこんな感じでしょうか?
def moduleA(): conf = Config() pramA = conf.ParameterA
ひとつのモジュールで使うだけならこれでもいいのですが、
複数のモジュールでこのクラスのパラメータを共有しなければならなくなったとします。
そこでモジュール毎にインスタンスを作ってみます。
こうしてしまうとインスタンスがそれぞれ独立しているので、あるインスタンスのパラメータを書き換えても別のインスタンスには反映されません。
def moduleA(): conf = Config() pramA = conf.ParameterA conf.ParameterA = 5 def moduleB(): conf = Config() pramA = conf.ParameterA conf.ParameterA = 6
今度はConfigをグローバル変数にしてみます。
こうすればmoduleAとmoduleBでインスタンスを共有することができます。
conf = Config() def moduleA(): pramA = conf.ParameterA conf.ParameterA = 5 def moduleB(): pramA = conf.ParameterA conf.ParameterA = 6
しかし、プログラムの規模が大きくなって複数人で開発するようになってきたりすると、
誰かがどこかでまたConfigのインスタンスを作ってしまったり、confに新しいインスタンスで上書きしてしまうかもしれません。
そうするとまた独立したインスタンスが複数できてしまうことになります。
conf = Config() ・ ・ ・ config = Config() # confの存在に気がつかずに新しいインスタンスを作成 def moduleA(): pramA = conf.ParameterA conf.ParameterA = 5 def moduleB(): pramA = conf.ParameterA conf = Config() # 誤ってインスタンスの上書き conf.ParameterA = 6 def moduleC(): pramA = conf.ParameterA config.ParameterA = 7 # confではないインスタンスを使用
このようなことを回避するには、一度インスタンスを作成したら、それ以降は新しいインスタンスを作成できないようにしなければなりません(;゚Д゚)
そこでSingletonパターンの登場です。イメージとしては以下のような感じです。
ConfigにクラスメソッドgetInstance()を実装しておきます。
getInstance()は、クラス内にインスタンスがなければクラス変数に自身のインスタンスを作成してそれを返します。
クラス変数に自身のインスタンスがすでに存在すれば、それをそのまま返します。
そしてConfig()でインスタンスを作成しようとしたときエラーを出力するようにします。
このようにしておけばConfigクラス内だけに存在する単一のインスタンスを作ることができます。
#conf = Config() # 新しいインスタンス生成はエラー def moduleA(): conf = Config.getInstance() # インスタンス取得 pramA = conf.ParameterA conf.ParameterA = 5 def moduleB(): #conf = Config() # 新しいインスタンス生成はエラー conf = Config.getInstance() # インスタンス取得 pramA = conf.ParameterA conf.ParameterA = 6
試行錯誤の時間
まずConfig()でインスタンスを作成できないようにしてみます。
Pythonではraise Exceptionで例外をスローできます。
そこで以下のようにしてコンストラクタが呼び出されたときにエラーを出力できるようにしてみました。
class Config(object) : def __init__(self) : raise Exception("Singletonクラス")
これでインスタンスを作成しようとするとエラーになります。
conf = Config() # エラーになる
つぎにインスタンスを取得するためのクラスメソッドgetInstance()を実装してみます。
この例ではクラス変数__mInstanceが空っぽだった場合、Config() で自身のインスタンスを作成するようにしています。
もうお気づきかもしれませんが、これでは困ったことがおきます。
先の実装で、Config()でインスタンスを作成しようとするとエラーを発生させるようにしてしまったからです(・´ω`・;)
class Config(object) : __mInstance = None def __init__(self) : raise Exception("Singletonクラス") @classmethod def getInstance(cls) : if cls.__mInstance == None : cls.__mInstance = Config() # エラー return cls.__mInstance
そこで__new__というものを利用します。
参考)__new__ ってなに?
def __new__(cls)は、インスタンスオブジェクトが作成される前に呼び出さる特殊なメソッドです。
以下、動作確認用のコードを書いてみたのでこれで動きを見てみます。
# coding: utf-8 class parent(object) : def __new__(cls) : print("new") return super().__new__(cls) def __init__(self) : print("init") def __del__(self) : print("del") if __name__ == '__main__': obj = parent()
これの実行結果は以下のようになります。
先に__new__が呼び出されて、__init__が呼び出されているのがわかります。
引数であるclsはクラスオブジェクトを示しており、super().__new__(cls)でクラスオブジェクトに対するインスタンスオブジェクトを作成することができます。
__new__(cls)のあとに__init__(self)が呼び出さるわけですが、selfはのsuper().__new__(cls)で作ったインスタンスオブジェクトだったわけですね。
(ちなみにreturn super().__new__(cls)を消せば、__init__(self)が呼び出されなくなります。当然ながら)
new init del
__new__を応用して以下のようにしてみました(・`ω´・)v
この例では、def __new__(cls)が呼び出されたときにraise Exceptionで例外をスローするようにしました。
そしてdef getInstance(cls)が呼び出されたとき、クラス変数__mInstanceが空っぽだった場合、
クラスオブジェクトを示すclsを使って、super().__new__(cls)でインスタンスを作成するようにしています。
class Config(object) : __mInstance = None def __new__(cls) : print("new") raise Exception("Singletonクラス") def __init__(self) : print("init") def __del__(self) : pass @classmethod def getInstance(cls) : if cls.__mInstance == None : cls.__mInstance = super().__new__(cls) return cls.__mInstance
これでConfig()でインスタンスを作成しようとすると以下のようなエラーを出力するようになります。
File "Singleton.py", line 7, in __new__ raise Exception("Singletonクラス") Exception: Singletonクラス
インスタンスの取得(未作成の場合は作成も)するには、以下のようにしてやらなければならなくなるはずです。
conf = Config.getInstance()
サンプルプログラム
以下、サンプルプログラムです。
# coding: utf-8 class _Singleton(object) : __mInstance = None def __new__(cls) : print("new parent") raise Exception("Singletonクラス") def __init__(self) : print("init parent") @classmethod def getInstance(cls) : if cls.__mInstance == None : cls.__mInstance = super().__new__(cls) return cls.__mInstance class myClass(_Singleton) : def __new__(cls) : print("new child") return super().__new__(cls) def __init__(self) : print("init child") def setValue(self, value) : self.__mValue = value def getValue(self) : return self.__mValue if __name__ == '__main__': #obj = Singleton() # エラー #obj = Singleton.getInstance() #obj1 = myClass() # エラー obj1 = myClass.getInstance() obj1.setValue(100) print(obj1.getValue()) obj2 = myClass.getInstance() print(obj2.getValue()) obj2.setValue(50) print(obj1.getValue())
実行結果はこのようになります!
myClass.getInstance()でobj1とobj2で同じインスタンを取得しています。
その後、それぞれでgetValue/setValueを行っていますが、printすればobj1とobj2で値が共有できていることがわかります。
$ python Singleton.py 100 100 50
- 価格: 2728 円
- 楽天で詳細を見る