2.

Python `__init__` メソッド完全ガイド(self / super / dataclass / __new__ との違い)

編集
この記事の要点
  • __init__ は Python クラスの初期化メソッド(コンストラクタ的役割)。インスタンス生成直後に自動で呼ばれる
  • 第一引数は必ず self(生成されたインスタンス自身)。引数名は慣習で self だが任意
  • 親クラスの初期化を呼ぶには super().__init__(...)。多重継承では MRO に従って呼ばれる
  • 実はコンストラクタ本体は __new____new__ がインスタンス生成、__init__ は初期化
  • 定型コードを減らすなら @dataclass(標準ライブラリ)や Pydantic Model(バリデーション付き)

__init__ の基本

__init__ は Python のクラスでインスタンスが生成された直後に自動で呼ばれる特殊メソッドです。Java/PHP のコンストラクタに相当する役割を果たします。

class User:
    def __init__(self, name: str, age: int):
        # self は生成されたインスタンス自身
        self.name = name
        self.age = age

# インスタンス生成と同時に __init__ が呼ばれる
u = User("taro", 30)
print(u.name)  # taro
print(u.age)   # 30

self キーワード

selfインスタンス自身への参照です。クラス定義内のメソッドの第一引数は必ず self(または同等の引数)を取ります。

class Counter:
    def __init__(self, start: int = 0):
        self.count = start          # インスタンス変数として保存

    def increment(self):
        self.count += 1             # self 経由でアクセス
        return self.count

c = Counter(10)
print(c.increment())  # 11
print(c.increment())  # 12

# self はメソッド呼び出し時に自動で渡される
# Counter.increment(c) と書いても同じ
print(Counter.increment(c))  # 13

引数の種類(デフォルト / キーワード / 可変長)

class Config:
    def __init__(
        self,
        host: str,                  # 必須位置引数
        port: int = 8080,           # デフォルト引数
        *plugins,                   # 可変長位置引数 (tuple)
        debug: bool = False,        # キーワード専用引数(* の後)
        **options                   # 可変長キーワード引数 (dict)
    ):
        self.host = host
        self.port = port
        self.plugins = plugins
        self.debug = debug
        self.options = options

c = Config(
    "localhost",
    8000,
    "cache", "auth",                # plugins に入る
    debug=True,
    timeout=30, retries=3,          # options に入る
)
print(c.plugins)  # ('cache', 'auth')
print(c.options)  # {'timeout': 30, 'retries': 3}

親クラスの __init__ を呼ぶ: super()

継承時、親クラスの初期化処理は明示的に呼ぶ必要があります(Java の暗黙呼び出しと違う)。

class Animal:
    def __init__(self, name: str):
        self.name = name
        self.alive = True

class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name)      # ★ 親の __init__ を呼ぶ
        self.breed = breed

d = Dog("Pochi", "Shiba")
print(d.name)   # Pochi  (親で設定)
print(d.breed)  # Shiba  (子で設定)
print(d.alive)  # True   (親で設定)

__init__ vs __new__

厳密には __new__インスタンスを生成するメソッドで、__init__生成後の初期化です。通常は __init__ だけ書きますが、イミュータブル型やシングルトン実装では __new__ をオーバーライドします。

メソッド役割第一引数戻り値
__new__インスタンス生成(メモリ確保)cls(クラス)新しいインスタンス
__init__生成済インスタンスの初期化self(インスタンス)None 必須
class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, value):
        # 注意: Singleton では __init__ が毎回呼ばれる
        self.value = value

a = Singleton(1)
b = Singleton(2)
print(a is b)     # True
print(a.value)    # 2  (__init__ が再実行されて上書きされる)

@dataclass で __init__ を自動生成

定型的な「フィールドを受け取って self に保存」だけなら @dataclass で書かずに済みます(Python 3.7+)。

from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    age: int = 0                          # デフォルト値
    tags: list[str] = field(default_factory=list)

# __init__ / __repr__ / __eq__ が自動生成される
u = User("taro", 30, ["admin"])
print(u)
# User(name='taro', age=30, tags=['admin'])

# 自動生成された __init__ をさらに拡張したいときは __post_init__
@dataclass
class Account:
    email: str
    domain: str = field(init=False)       # __init__ の引数から除外

    def __post_init__(self):
        # __init__ の後に自動で呼ばれる
        self.domain = self.email.split("@")[1]

a = Account("foo@example.com")
print(a.domain)  # example.com

Pydantic でバリデーション付き __init__

from pydantic import BaseModel, EmailStr, Field, ValidationError

class User(BaseModel):
    name: str = Field(min_length=1, max_length=50)
    age: int = Field(ge=0, le=150)
    email: EmailStr

# __init__ で型チェックとバリデーションが走る
try:
    u = User(name="taro", age=-1, email="invalid")
except ValidationError as e:
    print(e)
    # 2 validation errors for User
    # age: ensure this value is greater than or equal to 0
    # email: value is not a valid email address

Java / PHP のコンストラクタとの比較

言語名前self 引数親呼び出し
Python__init__あり (self)super().__init__()
Javaクラス名不要 (this)super() 暗黙呼び出しあり
PHP__construct不要 ($this)parent::__construct()
JavaScriptconstructor不要 (this)super()

__init_subclass__ でサブクラス登録パターン

class Plugin:
    registry = {}

    def __init_subclass__(cls, name: str = None, **kwargs):
        # サブクラス定義時に呼ばれる(インスタンス生成時ではない)
        super().__init_subclass__(**kwargs)
        if name:
            Plugin.registry[name] = cls

class JsonExporter(Plugin, name="json"):
    pass

class CsvExporter(Plugin, name="csv"):
    pass

print(Plugin.registry)
# {'json': <class '__main__.JsonExporter'>, 'csv': <class '__main__.CsvExporter'>}

FAQ

Q: __init__ は戻り値を返してよい?
A: None 以外を返すと TypeError。値を返したいなら __new__ を使います。

Q: self という名前は変えられる?
A: 文法上は可(def __init__(this, ...) も動く)。ただし PEP 8 と慣習に強く反するため避けてください。

Q: super().__init__() を呼び忘れたら?
A: 親クラスのインスタンス変数が未設定になるため、後で AttributeError が出ます。継承時は必ず呼ぶ習慣を。

Q: 引数が多すぎて __init__ が肥大化する
A: @dataclass / Pydantic / Builder パターン / from_dict クラスメソッドで分割を検討。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. クラスの定義
  2. initメソッド
  3. 関数の定義
  4. クラス変数とインスタンス変数

最近更新/作成されたページ