# __new__ ってなに?
# 1. __new__ と __init__ の違い
3つの違いがあります。
インスタンスオブジェクトが 生成される前に呼ばれます。 |
インスタンスオブジェクトが 生成された後に呼ばれます。 | |
オブジェクト self を インスタンス化します。 |
オブジェクト self を 初期化します。 | |
第一引数 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__ は、いつ使うの?
答え:
- immutable を初期化したい(本稿で解説)
- singleton を実装したい(Python でシングルトンを書く) (opens new window)
- 引数をもとにクラスを切り替えたいとき(メタクラスで紹介)
この記事では 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 について見て行きたいと思います。