初めに
この記事ではpythonを使ったsingletonにより,インスタンスの生成を一つに制限したいということをします.
私の環境は
- WSL, Ubuntu22.04.1
- Python 3.10.6
となっています.
singleton
まずsingletonはというのはデザインパターンの一つで,クラスのインスタンスの生成を制限するものです.ちなみにデザインパターンの私の理解は,先人の知恵的なオブジェクト指向のやつ,という程度です.
singletonでは自身のインスタンスをprivate変数として持ち,コンストラクタもprivateにすることでインスタンスの生成をユーザーから隠します.そして,private変数として持っているインスタンスをpublicなメソッドで返すことで,ユーザーにインスタンスを渡します.このようにしてsingletonはインスタンスを制限します.
作成
コメントにて,モジュールがsingletonとして機能することを教えていただきました.一度コメントを確認いただけると幸いです.
singletonの作成には,Pythonで、デザインパターン「Singleton」を学ぶという記事を参考にしました.
まずはsingletonとなるクラスを作っていきましょう.
Pythonにおいてprivateにする方法は変数名や関数名に「__」を付けることです.
追記(12/19):コメントにて,__
で始めても外からアクセスできることを指摘していただきました.ありがとうございます.
以下の実行例でのエラー文を見ると,AttributeError: 'Singleton' object has no attribute '__private'
となっていますので,やはり__private
という名前の変数となって生成がされていないようです.__private
という変数へのアクセスは_test__private
とするとできるようです.
class test:
def __init__(self) -> None:
self.public = "this is public"
self.__private = "this is private"
t = test()
print(t.public)
print(t._test__private)
print(t.__private)
$ python3 singleton.py
this is public
this is private
Traceback (most recent call last):
File "C.py", line 22, in <module>
print(t.__private)
AttributeError: 'test' object has no attribute '__private'
こんな感じでエラーが発生します.そのため,インスタンスをプライベートにすればよさそうですが,そもそもクラスの生成のメソッド自体が既にプライベートですしクラスの呼び出し時にインスタンスが勝手に生成されるので意味がないです.
追記(12/19):こちらも同じ方にコメントにて指摘していただきました.ありがとうございます.
__init__
で確認しましたが,実際にアクセスできました.
class test:
def __init__(self, item) -> None:
self.item = item
t = test(0)
print(t.item)
t.__init__(1)
print(t.item)
$ python3 test.py
0
1
そのため,先ほどの記事では,singletonのクラスは全てのクラスの基底クラスであるobject
のサブクラスとして実装しています.このobject
のインスタンスを生成の際に存在するかどうかを確認します.そして存在しない場合はobject
の方でインスタンスを生成し,そのインスタンスをsingletonのクラスの方で変数で保持しておき,存在する場合変数に保持しているインスタンスを返します.ちなみに,インスタンス生成のメソッドは__new__
というメソッドらしいです.
というわけで試してみます.
実験
class Singleton(object):
def __new__(cls):
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self) -> None:
self.item = 0
$ python3 singleton.py
<__main__.Singleton object at 0x7f4d6e3ee2c0>
0
<__main__.Singleton object at 0x7f4d6e3ee2c0>
0
10 10
このようにインスタンスの中身が一致し,item
の中身をs
で変更したのにs2
でも変更されています.また,インスタンスを保持しておく変数を_instance
としていますが,初期状態では存在していません.そのため組み込み関数であるhasattr
を利用して確認しています.
また,元記事ではget_instance
というメソッドを作ってオリジナルなsingletonを作成しています.そしてget_instance
を利用することでsingletonを作っています.しかしこれはユーザーが適切にget_instance
を利用することが前提となっています.また,__new__
メソッドを変更していないため,get_instance
を使わずにクラスのインスタンスを生成することができてしまいます.
つまり,元記事のコードを以下のように少し変更してみますと,singletonにはなりません.
class Singleton(object):
@classmethod
def get_instance(cls, input):
if not hasattr(cls, "_instance"):
cls._instance = cls(input)
else:
cls._instance.input = input
return cls._instance
class Myclass(Singleton):
def __init__(self, input):
self.input = input
if __name__ == "__main__":
one = Myclass.get_instance(1)
print("one.input={0}".format(one.input))
two = Myclass.get_instance(2)
print("one.input={0}, two.input={1}".format(one.input, two.input))
one.input = 0
print("one.input={0}, two.input={1}".format(one.input, two.input))
+ three = Myclass(3)
+ print(three.input, one.input, two.input)
$ python3 singleton.py
one.input=1
one.input=2, two.input=2
one.input=0, two.input=0
3 0 0
このように,three
のinput
では3が表示されますが,one, two
のinput
は以前の値である0が表示されています.つまり,本当の意味でのsingletonにはなりませんので注意してください.
以上となります.インスタンスの生成を一つに制限するためのsingletonを作成するための方法を紹介しました.
お疲れ様でした.