Pythonでクラスを学び始めると、最初に出てくるのが__init__とselfです。
しかし「なんとなく書いているけれど、本当の意味や設計への活かし方はよく分からない」という声も多いです。
本記事では、コンストラクタ__init__の仕組みとselfの正体を基礎からていねいに解説し、現場で役立つ応用パターンまで一気に整理していきます。
Pythonのコンストラクタ(__init__)とは
__init__とは何か

Pythonでは、クラスからインスタンスを生成する際に初期化処理を担当する特別なメソッドとして__init__が用意されています。
一般的には「コンストラクタ」と呼ばれますが、より正確にはインスタンスが生成されたあとに、そのインスタンスへ初期値をセットするための初期化メソッドです。
Pythonでクラスを書くときは、次のように__init__を定義します。
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)のようにクラス名を呼び出すと、内部では次のような流れで処理が進みます。
__new__が呼ばれ、「空っぽのインスタンス」がメモリ上に作られる- 作られたインスタンスが
__init__の第1引数selfとして渡される __init__の中で、属性に値を代入するなど初期化処理が行われる- 初期化済みのインスタンスが呼び出し元に返される
通常の開発では__new__を意識する必要はなく、「クラス呼び出し → __init__で初期化される」という流れを押さえておけば十分です。
__init__が自動的に呼ばれるタイミング

__init__は、クラス名を関数のように呼び出して新しいインスタンスを作るときに自動的に実行されます。
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は「そのメソッドを呼び出しているインスタンス自身」への参照です。
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というローカル変数が混在します。
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が判断できません。
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を使うことで、インスタンスにぶら下がっている属性(データ)やメソッド(処理)にアクセスできます。
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__の基本パターンと実用例
必須引数とオプション引数の定義方法

コンストラクタには、他の関数と同じように必須引数とオプション引数(デフォルト値付き引数)を定義できます。
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のデフォルト引数には注意点があります。
リストや辞書などのミュータブルなオブジェクトをデフォルト引数にしないことが重要です。
悪い例です。
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]
これは、デフォルト引数のリストが全インスタンスで共有されているためです。
正しい書き方は次のようになります。
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以降では、型ヒント(タイプヒント)を使ってコンストラクタの引数や属性の型を明示できます。
これにより、コードの可読性と保守性が大きく向上します。
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など)のサポートを受けやすくなり、バグの早期発見に役立ちます。
クラス変数とインスタンス変数の違いと使い分け

クラスにはクラス変数とインスタンス変数があります。
- クラス変数: クラスに属し、全インスタンスで共有される値
- インスタンス変数: 各インスタンスごとに異なる値を持つ属性
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__に書き込むと、すぐに肥大化して読みづらくなります。
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
バリデーションが増えてきたら、次のようにメソッドに切り出して責務を分割すると見通しが良くなります。
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)です。
必要になるまで実際の初期化を行わない設計です。
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
reader = LazyFileReader("example.txt")
# ここではまだファイルは開かれない
content = reader.read_all() # 初めて読むときにファイルを開く
reader.close()
「インスタンス生成時に必ず必要なもの」は__init__で初期化し、「使うかどうか分からない重い処理」は遅延初期化にすると、パフォーマンスと設計のバランスが良くなります。
@classmethodと別名コンストラクタ(alternative constructor)の実装

Pythonでは、@classmethodを使って「別名コンストラクタ(alternative constructor)」を実装するのがよくあるパターンです。
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で呼び出す必要があります。
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__を自動生成できます。
データを持つためのクラスを定義するときにとても便利です。
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__のあとに実行したい初期化処理を書くことができます。
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__まで理解しておくと、現場でのクラス設計がぐっと楽になります。
まずは小さなクラスから実際に書いて試しながら、自分なりの「初期化の設計パターン」を身につけていくと良いでしょう。
