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

# 初めに

この記事ではpythonを使ったsingletonにより，インスタンスの生成を一つに制限したいということをします．

私の環境は
- WSL, Ubuntu22.04.1
- Python 3.10.6

となっています．

# singleton
まずsingletonはというのはデザインパターンの一つで，クラスのインスタンスの生成を制限するものです．ちなみにデザインパターンの私の理解は，先人の知恵的なオブジェクト指向のやつ，という程度です．

singletonでは自身のインスタンスをprivate変数として持ち，コンストラクタもprivateにすることでインスタンスの生成をユーザーから隠します．そして，private変数として持っているインスタンスをpublicなメソッドで返すことで，ユーザーにインスタンスを渡します．このようにしてsingletonはインスタンスを制限します．

# 作成

:::note info

コメントにて，モジュールがsingletonとして機能することを教えていただきました．一度コメントを確認いただけると幸いです．

:::

singletonの作成には，[Pythonで、デザインパターン「Singleton」を学ぶ](https://qiita.com/ttsubo/items/c4af71ceba15b5b213f8#-singleton%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%83%88%E3%83%B3)という記事を参考にしました．

まずはsingletonとなるクラスを作っていきましょう．
~~Pythonにおいてprivateにする方法は変数名や関数名に「__」を付けることです．~~
追記(12/19)：コメントにて，`__`で始めても外からアクセスできることを指摘していただきました．ありがとうございます．
以下の実行例でのエラー文を見ると，`AttributeError: 'Singleton' object has no attribute '__private'`となっていますので，やはり`__private`という名前の変数となって生成がされていないようです．`__private`という変数へのアクセスは`_test__private`とするとできるようです．

```python: 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: 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__`で確認しましたが，実際にアクセスできました．
```python
class test:
    def __init__(self, item) -> None:
        self.item = item


t = test(0)
print(t.item)
t.__init__(1)
print(t.item)
```
```bash
$ python3 test.py 
0
1
```

そのため，[先ほどの記事](https://qiita.com/ttsubo/items/c4af71ceba15b5b213f8#-singleton%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%83%88%E3%83%B3)では，singletonのクラスは[全てのクラスの基底クラスである`object`](https://docs.python.org/ja/3.9/library/functions.html?highlight=object#object)のサブクラスとして実装しています．この`object`のインスタンスを生成の際に存在するかどうかを確認します．そして存在しない場合は`object`の方でインスタンスを生成し，そのインスタンスをsingletonのクラスの方で変数で保持しておき，存在する場合変数に保持しているインスタンスを返します．ちなみに，インスタンス生成のメソッドは[`__new__`というメソッド](https://docs.python.org/ja/3/reference/datamodel.html?highlight=__new__#object.__new__)らしいです．

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

## 実験

```python: 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: bash
$ python3 singleton.py 
<__main__.Singleton object at 0x7f4d6e3ee2c0>
0
<__main__.Singleton object at 0x7f4d6e3ee2c0>
0
10 10
```

このようにインスタンスの中身が一致し，`item`の中身を`s`で変更したのに`s2`でも変更されています．また，インスタンスを保持しておく変数を`_instance`としていますが，初期状態では存在していません．そのため[組み込み関数である`hasattr`](https://docs.python.org/ja/3/library/functions.html#hasattr)を利用して確認しています．


また，元記事では`get_instance`というメソッドを作って[オリジナルなsingleton](https://qiita.com/ttsubo/items/c4af71ceba15b5b213f8#2-%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%8A%E3%83%AB%E3%81%AAsingleton%E3%82%92%E6%9B%B8%E3%81%84%E3%81%A6%E3%81%BF%E3%82%8B)を作成しています．そして`get_instance`を利用することでsingletonを作っています．しかしこれはユーザーが適切に`get_instance`を利用することが前提となっています．また，`__new__`メソッドを変更していないため，`get_instance`を使わずにクラスのインスタンスを生成することができてしまいます．

つまり，元記事のコードを以下のように少し変更してみますと，singletonにはなりません．
```diff_python: 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: bash
$ 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を作成するための方法を紹介しました．
お疲れ様でした．