15.

Python のコンストラクタ|__init__ と __new__ の違いとオーバーロードの代替

編集
この記事の要点
  • Python のコンストラクタは __new__(生成)と __init__(初期化)の 2 段構え。普段書くのは __init__ だけ
  • クラス名と同じ名前のメソッドはコンストラクタにならない。ただのメソッドとして扱われる
  • オーバーロードはできない。同名で 2 つ定義すると後の 1 つだけが残る
  • 引数違いの生成はデフォルト引数@classmethod のファクトリで表現する
  • 継承したら super().__init__(...) を必ず呼ぶ。自動では呼ばれない

__init__ が初期化を担当する

class User:
    def __init__(self, name, age=0):
        self.name = name
        self.age = age
        print("初期化されました")

u = User("田中", 30)      # 初期化されました
print(u.name, u.age)      # 田中 30

User("田中", 30) と書いたとき、Python は内部で次の順に処理しています。

呼ばれるもの役割戻り値
1__new__(cls, ...)インスタンスを作る作ったインスタンス
2__init__(self, ...)作られたものを初期化するNone でなければならない

__init__return に値を書くと TypeError: __init__() should return None になります。「戻り値を返すコンストラクタ」を作りたい場合は次の @classmethod を使います。

オーバーロードはできない

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __init__(self, text):        # ← 上書きされる。エラーにもならない
        self.x, self.y = map(int, text.split(","))

Point(1, 2)      # TypeError: __init__() takes 2 positional arguments but 3 were given

Python の関数定義は名前への代入なので、同名で 2 回書くと後の定義だけが残ります。引数の数や型で切り替える仕組みはありません。

方法 1: デフォルト引数でまとめる

class Point:
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y

Point()          # (0, 0)
Point(1)         # (1, 0)
Point(1, 2)      # (1, 2)
Point(y=5)       # (0, 5)   キーワード指定もできる

方法 2: classmethod のファクトリ(推奨)

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    @classmethod
    def from_text(cls, text):        # "1,2" から作る
        x, y = map(int, text.split(","))
        return cls(x, y)

    @classmethod
    def origin(cls):                 # 原点を作る
        return cls(0, 0)

p1 = Point(1, 2)
p2 = Point.from_text("3,4")
p3 = Point.origin()

作り方ごとに名前が付くため、どの生成方法なのかが呼び出し側で一目で分かります。cls(...) と書いておけば、継承したクラスから呼んでも正しく子クラスのインスタンスが返ります。

継承したら super().__init__() を呼ぶ

class User:
    def __init__(self, name):
        self.name = name

class Admin(User):
    def __init__(self, name, level):
        super().__init__(name)       # ← これが無いと self.name が作られない
        self.level = level

a = Admin("田中", 3)
print(a.name, a.level)               # 田中 3

class Broken(User):
    def __init__(self, name, level):
        self.level = level           # super() を呼び忘れた

b = Broken("田中", 3)
print(b.name)                        # AttributeError: 'Broken' object has no attribute 'name'

子クラスで __init__ を定義しなければ、親の __init__ がそのまま使われます。定義した瞬間に親の初期化は行われなくなるので、super().__init__() の呼び忘れが最頻出のバグです。

__new__ を使う場面

__new__ を書く必要があるのは、イミュータブルな型を継承したとき生成そのものを制御したいときだけです。

class UpperStr(str):                 # str は生成後に中身を変えられない
    def __new__(cls, value):
        return super().__new__(cls, value.upper())

print(UpperStr("abc"))               # ABC

# シングルトン(常に同じ 1 個を返す)
class Config:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

print(Config() is Config())          # True

__new__そのクラスのインスタンス以外を返すと __init__ は呼ばれません。シングルトンでは __init__ が生成のたびに走る点にも注意が必要です(初期化済みフラグを持たせるか、__init__ を空にします)。

データの入れ物なら dataclass

from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    age: int = 0
    tags: list[str] = field(default_factory=list)   # 可変な既定値はこう書く

    def __post_init__(self):          # __init__ の直後に呼ばれる
        if self.age < 0:
            raise ValueError("age は 0 以上にしてください")

u = User("田中", 30)
print(u)          # User(name='田中', age=30, tags=[])

__init__ / __repr__ / __eq__ が自動生成されます。属性を並べるだけの __init__ を書いているなら @dataclass にできます。

なお tags: list[str] = [] と書くと ValueError: mutable default で弾かれます。dataclass は 変数 で説明した「デフォルト引数の共有」問題を防ぐため、default_factory を強制します。

デストラクタ(__del__)は当てにしない

class Handle:
    def __del__(self):
        print("破棄")            # いつ呼ばれるか保証がない

# 後始末は with 文で明示する
class Handle:
    def __enter__(self):
        print("開く")
        return self
    def __exit__(self, exc_type, exc, tb):
        print("閉じる")          # 例外が起きても必ず呼ばれる

with Handle():
    print("処理中")

ファイルや接続の解放は __del__ ではなく with 文(コンテキストマネージャ)で行います。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本的なルール
  2. 変数
  3. 演算子
  4. 標準ライブラリ
  5. 外部ライブラリ
  6. 制御構文
  7. リスト(配列)
  8. タプル
  9. セット
  10. 辞書(dict)
  11. クラスとメソッド
  12. 継承の概念と必要性
  13. 継承の構文
  14. コンストラクタ
  15. cookieの値の設定と取得
  16. 例外処理
  17. 例外を文字列で出力する方法
  18. httpリクエスト(curl)をする方法
  19. Responseオブジェクトの中身の確認
  20. 変数が空かどうか判定する方法
  21. タイムゾーンの設定と現在日時の取得と文字列化
  22. シングルクォーテーションとダブルクォーテーションの違い