Pythonの特殊メソッド__new__とは

特殊メソッド __new__について

Pythonには、「特殊メソッド」と呼ばれるものが存在します。
Pythonではこの特殊メソッドをオーバーライドし使用することで、自分が定義したクラスのインスタンスの振る舞いを細かく調整することが可能です。 その特殊メソッドの一つが__new__です。
__new__はインスタンスの生成時に呼ばれる特殊メソッドです。 他にも、インスタンスに関わる特殊メソッドとして、インスタンスの初期化時に呼ばれる私たちに馴染深い__init__メソッド、インスタンスの破壊時に呼ばれる__del__メソッドがあります。
__new__を考える上で 切り離せないのが特殊メソッド__init__です。__init__と__new__の役割の違いを見ていきましょう。

__new__と __init__の違い

__init__はどちらかというと他の言語(Javaなど)のコンストラクタ的なものなので、 __init__が呼び出されるとオブジェクトの生成+初期化が行われていると思われがちですが、Pythonの場合はオブジェクトの生成はnewメソッドで公開/カスタマイズされるもので、 __init__の役割はあくまで初期化です。(Javaなどはコンストラクタが呼び出されるとオブジェクトの生成が自動で行われ、コンストラクタ内の処理で初期化が行われます。)
なので、 __new__はインスタンス生成、__init__はインスタンス初期化という役割に明確な違いがあります。
当たり前ですが、名前通りの働きをしてくれていることになりますね。

__new__

  1. 新しいインスタンス生成時に呼ばれます。
  2. 第一引数にはクラス自身を表すclsをとります。

__init__

  1. initという言葉通りインスタンスが生成された後に初期化時に呼び出されます。
  2. 第一引数には生成されたインスタンス自身を表すselfをとります。

利用例

以下で __new__を使用する場合の利用例を示していきます。

1.シングルトンパターン

シングルトンパターンとはGoFデザインパターンの一種で生成されるインスタンスを一つに制限することです。
ロケールなどの処理で使用されるデザインパターンだそうですが、使われるのはあまり見ないかもしれません。

Singleton パターン - Wikipedia

# main.py
class Singleton():
    singleton = None

    # *argがないとSomeClassの__init__で引数を指定できない
    def __new__(cls, *arg, **kwargs):
        if cls.singleton is None:
            cls.singleton = super().__new__(cls)
        return cls.singleton

class SomeClass(Singleton):
    def __init__(self, name: str):
        self.__name = name

    @property
    def name(self):
        return self.__name

def main():
    a = SomeClass('Jone')
    print(f'a.name is {a.name}')
    print(a)

    b = SomeClass('Ken')
    print(f'a.name is {a.name}')
    print(f'b.name is {b.name}')
    print(a)
    print(b)

if __name__ == '__main__':
    main()
# 実行結果
a.name is Jone
<__main__.SomeClass object at 0x10214a150>
a.name is Ken
b.name is Ken
<__main__.SomeClass object at 0x10214a150>
<__main__.SomeClass object at 0x10214a150>

ちゃんと生成されたインスタンスが一つだけになっています。

2.イミュータブルなクラスの継承した時の初期化

strやtuppleなどイミュータブルなクラスを継承した場合はinitでは初期化できません。
そこで__new__を使用することによって擬似的に初期化(インスタンス生成時のカスタマイズ)を行います。

strの場合の例

修正前

# strの場合
class SomeClass(str):
    def __init__(self, args):
        self = 'Jone'

def main():
    a = SomeClass('Ken')
    print(a)
# 実行結果
Ken

修正後

class SomeClass(str):
    def __new__(cls, args):
        self = str.__new__(cls, 'Jone')
        return self

def main():
    a = SomeClass('Ken')
    print(a)
# 実行結果
Jone

tupleの場合の例

# tupleの場合
class SomeClass(tuple):
    def __init__(self, args):
        print(self[0] + self[1] + self[2])
        self[4] = 'San'

def main():
    SomeClass('Ken')
# 実行結果
TypeError: 'SomeClass' object does not support item assignment
class SomeClass(tuple):
    def __new__(cls, args):
        x = args[0]
        y = args[1]
        z = args[2]
        
        self = tuple.__new__(cls, (x, y, z, 'San'))
        return self

def main():
    a = SomeClass('Ken')
    print(a)
# 実行結果
('K', 'e', 'n', 'San')

こちらも積極的に使うことはないと思いますが、このようなことができます。

終わりに

今回はあまり見かけない特殊メソッド__new__について調べてみましたが、上の利用例の他にもメタプログラミングなどで__new__をよく使用することがあるみたいですが、あまりまだメタプログラミングについては詳しくないので割愛します。
調べた限りでは特殊メソッド__new__はそこまで積極的に使うことはなさそうですね。