0
1

More than 1 year has passed since last update.

インスタンス生成を一つに制限するためのsingleton

Last updated at Posted at 2022-12-18

初めに

この記事では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とするとできるようです.

singleton.py
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)
bash
$ 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__というメソッドらしいです.

というわけで試してみます.

実験

python
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
bash
$ 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にはなりません.

singleton.py
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)
bash
$ python3 singleton.py 
one.input=1
one.input=2, two.input=2
one.input=0, two.input=0
3 0 0

このように,threeinputでは3が表示されますが,one, twoinputは以前の値である0が表示されています.つまり,本当の意味でのsingletonにはなりませんので注意してください.

以上となります.インスタンスの生成を一つに制限するためのsingletonを作成するための方法を紹介しました.
お疲れ様でした.

0
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1