Python の __new__ と __init__ の違い




3つの違いがあります。

__new__
__init__
1
インスタンスオブジェクトが
生成されるに呼ばれます。
インスタンスオブジェクトが
生成されたに呼ばれます。
2
オブジェクト self を
インスタンスします。
オブジェクト self を
初期化します。
3
第一引数 cls
クラスオブジェクトが
代入されます。
第一引数 self
インスタンスオブジェクトが
代入されます。


class Cls(object):
    def __new__(cls):
        self = super().__new__(cls)
        print('__new__ :', str(id(self)))
        return self
    
    def __init__(self):
        print('__init__:', str(id(self)))
        self.attr = 'Hello, world!'

obj = Cls()
>>> obj = Cls()
__new__ : 4525721080
__init__: 4525721080
>>> 

2. __new__ は、例えば、いつ使うの?

答え:

  1. immutable を初期化したい(本稿で解説)
  2. singleton を実装したい(Python で良い感じのシングルトンを書く)

◯ immutable って何?

◯ 具体例

例えば namedtuple 関数を使って immutable なオブジェクトを生成した時、値を初期化するための処理が実行できません。

class Region(namedtuple('Region', 
      ('x1', 'y1', 'x2', 'y2',
       'is_rectangle', 'is_line', 'is_dot'))):
    def __init__(self, x1, y1, x2, y2):
        width_0 = (x1 - x2 == 0)
        height_0 = (y1 - y2 == 0)
        self.is_rectangle = (not width_0 and not height_0)  # 0 0
        self.is_line = (width_0 != height_0)  # 0 1 or 1 0; xor
        self.is_dot = (width_0 and height_0)  # 1 1

# TypeError
Region(0, 0, 10, 10)


先に親クラスの __new__ が呼ばれている。オブジェクトを生成する前に全ての値を用意する必要がある。

>>> Region(0, 0, 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __new__() missing 3 required positional arguments: 'is_rectangle', 'is_line', and 'is_dot'
>>>


仮に __init__ が呼び出せたとしても、self は immutable なので代入することはできません。

◯ 改善策

import collections

class Region(collections.namedtuple('ImmutableRegion', 
      ('x1', 'y1', 'x2', 'y2',
       'is_rectangle', 'is_line', 'is_dot'))):
    def __new__(cls, x1, y1, x2, y2):
        # point 1. __init__ ではなく、__new__ を使う。
        width_0 = (x1 - x2 == 0)
        height_0 = (y1 - y2 == 0)
        is_rectangle = (not width_0 and not height_0)  # 0 0
        is_line = (width_0 != height_0)  # 0 1 or 1 0; xor
        is_dot = (width_0 and height_0)  # 1 1
        args = (x1, y1, x2, y2, is_rectangle, is_line, is_dot)
        
        # point 2. 必要なオブジェクトが揃ったところで
        #          オブジェクトを生成する。
        self = super().__new__(cls, *args)
        
        # point 3. 生成したオブジェクトを return
        return self

Region(0, 0, 1, 1)


なお typing.NamedTuple を継承したクラスは __new__ を上書きできません。

>>> # はじかれました。
>>> python smaple.py
...
AttributeError: Cannot overwrite NamedTuple attribute __new__
>>>

クラスデコレータとメタクラス

クラスデコレータあるいはメタクラスを使って immutable にする方法を紹介します。



おわりに

mutable と immutable の違いについて説明していたページで、何気なく「値」という言葉を使っていました。では、この「値」という言葉は何者でしょうか?

従って immutable であること (immutability) は、厳密に言えば "値が変更できないこと" と同義ではなく、もう少し複雑です。
So, immutability is not strictly the same as having an unchangeable value, it is more subtle.
3.1. Objects, values and types - The Python Language Reference


f:id:domodomodomo:20180514165854j:plain