閉じる

【Python】__init__コンストラクタの使い方|selfの正体から応用パターンまで

Pythonでクラスを学び始めると、最初に出てくるのが__init__selfです。

しかし「なんとなく書いているけれど、本当の意味や設計への活かし方はよく分からない」という声も多いです。

本記事では、コンストラクタ__init__の仕組みとselfの正体を基礎からていねいに解説し、現場で役立つ応用パターンまで一気に整理していきます。

Pythonのコンストラクタ(__init__)とは

__init__とは何か

Pythonでは、クラスからインスタンスを生成する際に初期化処理を担当する特別なメソッドとして__init__が用意されています。

一般的には「コンストラクタ」と呼ばれますが、より正確にはインスタンスが生成されたあとに、そのインスタンスへ初期値をセットするための初期化メソッドです。

Pythonでクラスを書くときは、次のように__init__を定義します。

Python
class User:
    def __init__(self, name, age):
        # インスタンスが作られた直後に呼ばれる初期化処理
        self.name = name
        self.age = age

# インスタンス生成
user = User("Alice", 30)
print(user.name, user.age)
実行結果
Alice 30

この例のように、__init__はインスタンス生成のタイミングで自動的に呼び出され、属性の初期値をセットする役割を担います。

インスタンス生成の流れ

PythonでUser("Alice", 30)のようにクラス名を呼び出すと、内部では次のような流れで処理が進みます。

  1. __new__が呼ばれ、「空っぽのインスタンス」がメモリ上に作られる
  2. 作られたインスタンスが__init__の第1引数selfとして渡される
  3. __init__の中で、属性に値を代入するなど初期化処理が行われる
  4. 初期化済みのインスタンスが呼び出し元に返される

通常の開発では__new__を意識する必要はなく、「クラス呼び出し → __init__で初期化される」という流れを押さえておけば十分です。

__init__が自動的に呼ばれるタイミング

__init__は、クラス名を関数のように呼び出して新しいインスタンスを作るときに自動的に実行されます。

Python
class Logger:
    def __init__(self, name):
        print(f"Logger({name}) を初期化します")

# ここで __init__ が自動的に呼ばれる
logger = Logger("app")
実行結果
Logger(app) を初期化します

一方で、次のような場合には__init__は呼ばれません。

  • すでにあるインスタンスを代入するだけのとき
  • copyモジュールやシリアライズ処理で内部的にインスタンスを復元しているとき(実装方法による)

そのため、「インスタンス生成のたびに必ずやってほしい処理」だけを__init__に書くという意識が重要です。

selfの正体と基本的な使い方

selfとは何か

Pythonでは、インスタンスメソッドの第1引数としてselfを書くのが慣習です。

selfは「そのメソッドを呼び出しているインスタンス自身」への参照です。

Python
class User:
    def greet(self):
        # self は「greet を呼び出したインスタンス」
        print(f"こんにちは、私は {self.name} です")

    def set_name(self, name):
        # self.name はインスタンスの属性
        self.name = name

user = User()
user.set_name("Alice")
user.greet()
実行結果
こんにちは、私は Alice です

このときuser.greet()は内部的にUser.greet(user)として呼ばれ、selfにはuserインスタンスが渡されます。

selfとローカル変数の違い

メソッドの中では、self.nameのようにself.でアクセスする値と、単なるnameというローカル変数が混在します。

Python
class Sample:
    def set_value(self, value):
        local_value = value          # これはメソッド内だけのローカル変数
        self.stored_value = value    # これはインスタンスの属性
        print("local_value:", local_value)
        print("self.stored_value:", self.stored_value)

s = Sample()
s.set_value(10)

# メソッドの外からアクセスできるのは self 経由の属性だけ
print("外から:", s.stored_value)
# print(local_value)  # これはエラーになる
実行結果
local_value: 10
self.stored_value: 10
外から: 10

ローカル変数はメソッドの実行が終わると消えますが、selfの属性として保存した値はインスタンスに保持され続けます

クラス設計では、どの値をローカルに閉じ込め、どの値をインスタンスの状態として持たせるかを意識することが重要です。

なぜselfを明示的に書く必要があるのか

多くのオブジェクト指向言語では、インスタンスメソッドの中でthisなどが暗黙的に渡されます。

しかしPythonでは、インスタンスメソッドの第1引数にselfを書くことがルールになっています。

これは、「メソッドはただの関数であり、第1引数にインスタンスを受け取る」というシンプルなモデルを採用しているからです。

selfを書かないと、どのインスタンスに対する処理なのかをPythonが判断できません。

Python
class Example:
    # self を書かないとエラーになる例
    def hello():  # ← self がない
        print("hello")

# Example().hello()  # TypeError になる
実行結果
TypeError: Example.hello() takes 0 positional arguments but 1 was given

このように、インスタンスメソッドは実際にはExample.hello(instance)という形で呼び出されるため、selfを明示的に受け取る必要があります。

selfを使った属性とメソッドのアクセス

selfを使うことで、インスタンスにぶら下がっている属性(データ)メソッド(処理)にアクセスできます。

Python
class Rectangle:
    def __init__(self, width, height):
        self.width = width    # 属性(データ)
        self.height = height  # 属性(データ)

    def area(self):
        # self 経由で属性にアクセスして計算
        return self.width * self.height

    def show_info(self):
        # 同じクラス内のメソッドも self 経由で呼び出せる
        print(f"幅: {self.width}, 高さ: {self.height}, 面積: {self.area()}")

rect = Rectangle(3, 4)
rect.show_info()
実行結果
幅: 3, 高さ: 4, 面積: 12

「self.○○」と書かれていたら、そのインスタンスが持っているデータや処理を使っていると理解するとよいです。

__init__の基本パターンと実用例

必須引数とオプション引数の定義方法

コンストラクタには、他の関数と同じように必須引数オプション引数(デフォルト値付き引数)を定義できます。

Python
class Mailer:
    def __init__(self, host, port, use_tls=False):
        # host, port は必須
        self.host = host
        self.port = port

        # use_tls はオプション(デフォルト False)
        self.use_tls = use_tls

mailer1 = Mailer("smtp.example.com", 587)
mailer2 = Mailer("smtp.example.com", 465, use_tls=True)

print(mailer1.host, mailer1.port, mailer1.use_tls)
print(mailer2.host, mailer2.port, mailer2.use_tls)
実行結果
smtp.example.com 587 False
smtp.example.com 465 True

必須引数はクラスを使う人に必ず指定してもらいたい情報オプション引数はよくあるパターンにデフォルト値で対応できる情報という意識で設計します。

デフォルト引数付きコンストラクタの書き方

Pythonのデフォルト引数には注意点があります。

リストや辞書などのミュータブルなオブジェクトをデフォルト引数にしないことが重要です。

悪い例です。

Python
class BadExample:
    def __init__(self, items=[]):  # ← 良くない
        self.items = items

a = BadExample()
b = BadExample()
a.items.append(1)

print("a.items:", a.items)
print("b.items:", b.items)  # b まで 1 が見えてしまう
実行結果
a.items: [1]
b.items: [1]

これは、デフォルト引数のリストが全インスタンスで共有されているためです。

正しい書き方は次のようになります。

Python
class GoodExample:
    def __init__(self, items=None):
        # None のときだけ新しいリストを作る
        if items is None:
            items = []
        self.items = items

a = GoodExample()
b = GoodExample()
a.items.append(1)

print("a.items:", a.items)
print("b.items:", b.items)  # こちらは独立
実行結果
a.items: [1]
b.items: []

ミュータブルな引数にはデフォルトでNoneを使い、中で新しいオブジェクトを生成するのが定石です。

型ヒント付き__init__で可読性と保守性を高める

Python3以降では、型ヒント(タイプヒント)を使ってコンストラクタの引数や属性の型を明示できます。

これにより、コードの可読性と保守性が大きく向上します。

Python
from typing import Optional, List

class Article:
    def __init__(
        self,
        title: str,
        body: str,
        tags: Optional[List[str]] = None,
        published: bool = False,
    ) -> None:
        self.title: str = title
        self.body: str = body
        self.tags: List[str] = tags if tags is not None else []
        self.published: bool = published

型ヒントを付けることで、エディタの補完や静的解析ツール(mypyなど)のサポートを受けやすくなり、バグの早期発見に役立ちます

クラス変数とインスタンス変数の違いと使い分け

クラスにはクラス変数インスタンス変数があります。

  • クラス変数: クラスに属し、全インスタンスで共有される値
  • インスタンス変数: 各インスタンスごとに異なる値を持つ属性
Python
class Counter:
    # クラス変数(全インスタンスで共有)
    total_count = 0

    def __init__(self):
        # 新しいインスタンスが作られるたびにクラス変数を更新
        Counter.total_count += 1
        # インスタンス変数(このインスタンスだけのカウンタ)
        self.my_id = Counter.total_count

c1 = Counter()
c2 = Counter()
c3 = Counter()

print("total_count:", Counter.total_count)
print("c1.my_id:", c1.my_id)
print("c2.my_id:", c2.my_id)
print("c3.my_id:", c3.my_id)
実行結果
total_count: 3
c1.my_id: 1
c2.my_id: 2
c3.my_id: 3

「全体で共有したい設定値や統計情報」はクラス変数として、「各インスタンス固有の状態」はインスタンス変数として持たせると整理しやすくなります。

バリデーションを__init__に書くときの注意点

__init__の中で引数の値をチェック(バリデーション)するのは一般的です。

しかし、バリデーションロジックをすべて__init__に書き込むと、すぐに肥大化して読みづらくなります

Python
class User:
    def __init__(self, name: str, age: int):
        # バリデーション(簡単な例)
        if not name:
            raise ValueError("name は空にできません")
        if age < 0:
            raise ValueError("age は 0 以上にしてください")

        self.name = name
        self.age = age

バリデーションが増えてきたら、次のようにメソッドに切り出して責務を分割すると見通しが良くなります。

Python
class User:
    def __init__(self, name: str, age: int):
        self._validate_name(name)
        self._validate_age(age)
        self.name = name
        self.age = age

    def _validate_name(self, name: str) -> None:
        if not name:
            raise ValueError("name は空にできません")

    def _validate_age(self, age: int) -> None:
        if age < 0:
            raise ValueError("age は 0 以上にしてください")

バリデーションを__init__に書くときは、「コンストラクタの責務はインスタンスを正しい状態で生成すること」という原則を意識し、読みやすさとのバランスを取ることが大切です。

__init__の応用パターンと設計テクニック

__init__での初期化処理と遅延初期化

__init__では、ファイルを開いたりネットワーク接続を確立したりと、さまざまな初期化処理を行えます。

ただし、重い処理をすべて__init__に詰め込むと、インスタンス生成が遅くなりがちです。

そこで有効なのが遅延初期化(lazy initialization)です。

必要になるまで実際の初期化を行わない設計です。

Python
class LazyFileReader:
    def __init__(self, path: str):
        self.path = path
        self._file = None  # 最初は開かない(遅延初期化)

    def _open_if_needed(self):
        if self._file is None:
            print("ファイルを開きます")
            self._file = open(self.path, "r", encoding="utf-8")

    def read_all(self) -> str:
        self._open_if_needed()
        return self._file.read()

    def close(self):
        if self._file is not None:
            self._file.close()
            self._file = None
Python
reader = LazyFileReader("example.txt")
# ここではまだファイルは開かれない
content = reader.read_all()  # 初めて読むときにファイルを開く
reader.close()

「インスタンス生成時に必ず必要なもの」は__init__で初期化し、「使うかどうか分からない重い処理」は遅延初期化にすると、パフォーマンスと設計のバランスが良くなります。

@classmethodと別名コンストラクタ(alternative constructor)の実装

Pythonでは、@classmethodを使って「別名コンストラクタ(alternative constructor)」を実装するのがよくあるパターンです。

Python
import json
from typing import Dict, Any

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

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "User":
        # dict から User を生成する別名コンストラクタ
        return cls(name=data["name"], age=data["age"])

    @classmethod
    def from_json(cls, text: str) -> "User":
        # JSON 文字列から User を生成する別名コンストラクタ
        data = json.loads(text)
        return cls.from_dict(data)

user1 = User("Alice", 30)
user2 = User.from_dict({"name": "Bob", "age": 25})
user3 = User.from_json('{"name": "Charlie", "age": 20}')

print(user1.name, user2.name, user3.name)
実行結果
Alice Bob Charlie

別名コンストラクタでは、最終的にcls(…)で本来のコンストラクタ__init__を呼び出すのがポイントです。

これにより、インスタンス生成ロジックを1か所(__init__)に集約できます。

継承時の__init__

クラスを継承するとき、子クラスで__init__を定義した場合は親クラスの__init__をsuperで呼び出す必要があります。

Python
class Animal:
    def __init__(self, name: str):
        self.name = name
        print("Animal を初期化")

class Dog(Animal):
    def __init__(self, name: str, breed: str):
        print("Dog を初期化(親の前)")
        # 親クラスの __init__ を呼び出す
        super().__init__(name)
        print("Dog を初期化(親の後)")
        self.breed = breed

dog = Dog("Pochi", "Shiba")
print(dog.name, dog.breed)
実行結果
Dog を初期化(親の前)
Animal を初期化
Dog を初期化(親の後)
Pochi Shiba

もしsuper().__init__(name)を呼ばなければ、親クラス側の初期化処理が実行されず、必要な属性が設定されないといった不具合につながります。

また、多重継承を使う場合は、super()を使うことでメソッド解決順序(MRO)に沿った<r>協調的な初期化</r>が可能になります。

データクラス(dataclasses)で__init__を自動生成する

Python3.7以降では、dataclassesを使って__init__を自動生成できます。

データを持つためのクラスを定義するときにとても便利です。

Python
from dataclasses import dataclass
from typing import List

@dataclass
class Point:
    x: float
    y: float
    tags: List[str] | None = None

p1 = Point(1.0, 2.0)
p2 = Point(3.0, 4.0, tags=["A", "B"])

print(p1)
print(p2)
実行結果
Point(x=1.0, y=2.0, tags=None)
Point(x=3.0, y=4.0, tags=['A', 'B'])

このコードでは__init__を一切書いていませんが、@dataclassが自動的に__init__や__repr__などを生成してくれます。

単なる「値の入れ物」としてのクラスなら、手書きの__init__よりデータクラスを使う方がシンプルになることが多いです。

__post_init__との使い分けと初期化ロジックの整理

データクラスでは、__post_init__メソッドを定義することで、自動生成された__init__のあとに実行したい初期化処理を書くことができます。

Python
from dataclasses import dataclass, field

@dataclass
class Product:
    name: str
    price: int
    tags: list[str] = field(default_factory=list)

    def __post_init__(self):
        # バリデーション
        if self.price < 0:
            raise ValueError("price は 0 以上にしてください")

        # 正規化(例: 名前の前後の空白を削除)
        self.name = self.name.strip()

p = Product("  Apple  ", 120)
print(p.name, p.price, p.tags)
実行結果
Apple 120 []

データクラスを使うときは、フィールド宣言はクラス本体に、初期化後の加工やバリデーションは__post_init__にと役割を分けると、「何を保持するのか」と「どのように初期化するのか」をきれいに整理できます。

まとめ

Pythonの__init__selfは、オブジェクト指向設計の基盤となる重要な要素です。

selfは「インスタンス自身」への参照であり、__init__はそのインスタンスを正しい状態に初期化するためのメソッドです。

必須引数とオプション引数、クラス変数とインスタンス変数、バリデーションや遅延初期化、別名コンストラクタや継承、さらにはdataclassesと__post_init__まで理解しておくと、現場でのクラス設計がぐっと楽になります。

まずは小さなクラスから実際に書いて試しながら、自分なりの「初期化の設計パターン」を身につけていくと良いでしょう。

クラスとオブジェクト指向

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!