Last Updated: 2/6/2024, 5:44:57 AM

# __new__ ってなに?

# 1. __new__ と __init__ の違い




3つの違いがあります。

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

#
# 対話モード >>> に
# コピペで実行できます。
#
class Cls(object):
    def __new__(cls):
        self = object.__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 でシングルトンを書く) (opens new window)
  3. 引数をもとにクラスを切り替えたいとき(メタクラスで紹介)

この記事では immutable なオブジェクトを初期化する際に __new__ の使い方について説明させていただきます。 immutable については以下の記事で説明させていただきました。

実際に immutable なクラスを定義したいときは namedtuple (opens new window) 関数を使います。 このページでは tuple を継承した immutable なクラスで __new__ の簡単な使い方をご紹介します。

namedtuple については、次のページでご紹介いたしますが、 namedtuple の仕組みは、ここで紹介する tuple を継承する形で immutable を実現しています。

# 3. super - インスタンスを生成する。

tuple を継承したクラスを見る前に、簡単に super の挙動を振り返りたいと思います。

__new__ メソッドは、インスタンスを生成することを目的にしています。 __init__ メソッドは、インスタンスを初期化することを目的にしています。

__init__ の場合、第一引数 self にすでにインスタンスが 代入されているためインスタンスを自分で生成する必要はありません。 __init__ でもし何かをするとしたら、属性に値を代入するだけです。

    def __init__(self):  # <--- self にインスタンスが代入されている。
        print('__init__:', str(id(self)))
        self.attr = 'Hello, world!'

一方で __new__ の場合は、自分でインスタンスを生成しなければなりません。 そして生成したインスタンスを return します。

    def __new__(cls):
        self = object.__new__(cls)
        print('__new__ :', str(id(self)))
        return self

書籍では親クラスのメソッドを呼び出す場合、 super() を使うように指示されています。

class Cls(object):
    def __new__(cls):
        self = super().__new__(cls)

super は多重継承されていた際に、 いい塩梅に親クラスのメソッドを呼び出してくれる機能です。

今回は多重継承していないので、 super().__new__(cls) としても object.__new__(cls) としても、 同じです。

理解できていないのですが super の動作の意味は、ここがわかりやすいです。

super は super 難しいです。

「三合会は超サイコー」だとよ。張の旦那、身なりもセンスも一流だが、シャレだけはとことん最悪だ。
レヴィ - BLACK LAGOON

ちなみに、通常は super() は省略記法です。 省略しない場合は以下のように書きます。

class Cls(object):
    def __new__(cls):
        self = super(cls, cls).__new__(cls)

# 4. tuple を継承する

# 4.1. __init__ で初期化してみる

tuple を継承したとき __init__ では初期化できません。 なぜなら tuple は immutable で変更できないからです。

矩形を表現する ImmutableRegion というクラスについて考えます。 このクラスには座標を表す (x1, y1, x2, y2) だけでなく、 点 is_dot であるか、線 is_line であるか、矩形 is_rectangle であるかを表す属性を持たせたいとします。

頻繁に参照するためメソッドではなく事前に定義したい属性ということにしましょう。

class ImmutableRegion(tuple):
    def __init__(self, args):
        x1 = self[0] 
        y1 = self[1]
        x2 = self[2]
        y2 = self[3]
        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
        self[4] = is_rectangle  # <--- TypeError
        self[5] = is_line
        self[6] = is_dot

immutable_region = ImmutableRegion((2, 3, 3, 4))
>>> immutable_region = ImmutableRegion((2, 3, 3, 4))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 12, in __init__
TypeError: 'ImmutableRegion' object does not support item assignment
>>> 

ここで困ったことに tuple は immutable なので 要素を変更することができません... そこで __new__ を使います。

# 4.2. __new__ を使って初期化する

class ImmutableRegion(tuple):
    def __new__(cls, args):
        x1 = args[0] 
        y1 = args[1]
        x2 = args[2]
        y2 = args[3]
        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
        #
        # super().__new__ ... でも同じ
        self = tuple.__new__(cls, (
            x1, y1, x2, y2, is_rectangle, is_line, is_dot
        ))
        #
        return self

immutable_region = ImmutableRegion((2, 3, 3, 4))
immutable_region
>>> immutable_region
(2, 3, 3, 4, True, False, False)
>>> 

# 5. property で属性参照させる。

添字表記 [0] では辛いので属性参照できるように property を利用しました。

#
# 対話モード >>> に
# コピペで実行できます。
#
class ImmutableRegion(tuple):
    def __new__(cls, args):
        x1 = args[0] 
        y1 = args[1]
        x2 = args[2]
        y2 = args[3]
        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
        # 1. インスタンスを自分で生成して
        # super().__new__ ... でも同じ
        self = tuple.__new__(cls, (
            x1, y1, x2, y2, is_rectangle, is_line, is_dot
        ))
        # 2. インスタンスを返す
        return self
    
    def __repr__(self):
        x1, y1, x2, y2 = self.x1, self.y1, self.x2, self.y2
        return type(self).__name__ + f'{x1, y1, x2, y2}'
    
    x1           = property(lambda self: self[0])
    y1           = property(lambda self: self[1])
    x2           = property(lambda self: self[2])
    y2           = property(lambda self: self[3])
    is_rectangle = property(lambda self: self[4])
    is_line      = property(lambda self: self[5])
    is_dot       = property(lambda self: self[6])

immutable_region = ImmutableRegion((2, 3, 3, 4))
immutable_region
immutable_region.is_rectangle
immutable_region.is_line
immutable_region.is_dot
>>> immutable_region
ImmutableRegion(2, 3, 3, 4)
>>> immutable_region.is_rectangle
True
>>> immutable_region.is_line
False
>>> immutable_region.is_dot
False
>>> 

tuple を継承して property で参照するという方法は、 collections.namedtuple と原理的には全く同じ方法です。

# おわりに

ここまで __new__ を見てきました。 ここから次のページでは collections.namedtuple について見て行きたいと思います。