勇者だって最初はひのきの棒とナベのふた

技術を中心に発信。時々SFの読書感想も。

__init__, __new__? ちょっとだけ整理。

はじめに

masamoriです。
今回はPythonのお話。
もう何番煎だよっていう話ですが、やっと少し理解したので備忘録的に。もし奇跡的にこのブログを読んでくださってる方で、僕が間違ってるなら指摘をお願いします。

__init____new__

どっちもPythonインスタンスを作成する際に関わってくる重要なメソッド 以下原則.

例えば

 class Init:
     def __init__(self, value):
         self.value = value
  
 init1 = Init(1)
 init2 = Init(2)
 
 print(init1 == init2)  # False, 異なるインスタンス
 print(init1.value)  # 1, 初期化時の値
 print(init2.value)  # 2, 初期化時の値

となる。 このとき、インスタンス自体は生成されてるのでInitクラスの基底のobjectクラスのnewが呼ばれています。 つまり、クラス自体objectクラスを継承したものであるということが分かります。 ちなみに、objectクラスの確認の仕方は

 print(Init.__bases__) # <class 'object'>

__new__を知るために

__new__を直接編集することは、大体どの書籍見ても推奨されていません。
しかし、シングルトンパターンのPythonでの実装例を調べてみると、__new__を使って表現している人が多くいます。
んで、その間違った書き方は以下の通り。

class Singleton:
     _instance = None
 
     def __new__(cls, *args, **kwargs):
         print("cls_instance: ", cls._instance)
         if not cls._instance:
             # 最初のインスタンス生成時にのみ呼び出される
             cls._instance = super().__new__(cls)
         return cls._instance
 
     def __init__(self, value):
         self.value = value
 
 
 singleton1 = Singleton(1)
 singleton2 = Singleton(2)
 
 print(singleton1 == singleton2)  # True, 同じインスタンスを指す
 print(singleton1.value)  # 2, singleton2によって設定された値
 print(singleton2.value) # 2, singleton2によって設定された値

うーん、グローバル変数的な挙動をするんですね。
これは__new__をオーバーライドしたメソッドです。インスタンスが作られたかどうかチェックして、作成されていたらそのインスタンスを_instance変数に保持してします。二回目の呼び出しでは、インスタンスは既に作成されて保持されていますから、新たなインスタンスは作成されません。__new__が呼び出されないからね。
というわけで、この場合は__init__だけ呼び出されてインスタンスの値が書き換えられてしまいます。 つまりこの呼び出しの場合場合、実質init単体しか動いていないことになるってことですね。

一方、initだけを実装した場合、インスタンスinitが呼び出されるたびに作成されます。このときnewもその回数分だけ呼ばれます。この場合、objectクラスの__new__にはインスタンスを保持する処理は施されてないので、インスタンスの値は上書きされることはありません。
別メモリ空間に書くインスタンスが保持されるわけですね。

最後に

ちなみに、シングルトンパターンはnewを使わずに書くことができます。

 class Singleton:
     _instance = None
 
     @classmethod
     def get_instance(cls):
         if cls._instance is None:
             cls._instance = cls()
         return cls._instance

こっちの方がシンプルで好き。