オブジェクト指向でクラスを使い始めると、インスタンス生成時にどの値を持たせるかが重要になります。
その初期設定を担うのが__init__()
です。
本記事では、__init__
の役割や書き方、実用的な初期化パターン、よくあるエラーと解決策まで、初心者の方が段階的に理解できるよう丁寧に解説します。
Pythonのコンストラクタ(init)の役割
__init__の意味と動作タイミング
__init__
はインスタンス生成直後に自動的に呼び出される初期化メソッドです。
厳密には「コンストラクタ(インスタンス生成)」は__new__
で、__init__
は生成済みのインスタンスに初期値を設定する「イニシャライザ」です。
戻り値は常にNone
である必要があり、値を返しません。
次のコードは、__new__
と__init__
の呼び出し順を確認するものです。
class Demo:
def __new__(cls, *args, **kwargs):
print("__new__ が呼ばれました")
# 実際のインスタンス生成は super().__new__(cls) で行う
return super().__new__(cls)
def __init__(self):
print("__init__ が呼ばれました")
# ここで属性の初期化を行う
self.ready = True
d = Demo() # クラス呼び出しで __new__ → __init__ の順に進む
print("ready:", d.ready)
__new__ が呼ばれました
__init__ が呼ばれました
ready: True
オブジェクト生成から初期化までの流れ
クラス呼び出しから属性が使える状態になるまでの主要な流れを段階的に説明します。
まずClassName(...)
が実行されると、Pythonは__new__
でインスタンスの実体(空の器)を確保します。
次に、そのインスタンスを引数self
として__init__
が呼ばれ、属性の設定や検証などの初期化処理が実施されます。
ここで例外を投げると初期化は中断され、インスタンスは完成しません。
正常に終了すると、アプリケーションから利用可能な完成済みのオブジェクトが返されます。
以下の表は要点をまとめたものです。
ステップ | 説明 |
---|---|
1 | ClassName(...) が呼ばれる |
2 | __new__ でインスタンスの枠が作られる |
3 | __init__ で属性初期化や検証を行う |
4 | 完成したインスタンスが呼び出し元に返る |
初期化で何を設定すべきか
初期化では、そのインスタンスが「不変条件(invariant)」を満たすように必要十分な情報を設定します。
具体的には、外部から受け取った引数を属性に保存し、常に正しい状態で使えるよう最低限の検証を行います。
さらに、後続処理で頻繁に使う派生値(計算済み値)を準備しておくとパフォーマンスや可読性が向上します。
必要に応じて、ファイルや接続などのリソース確保もここで行いますが、ライフサイクル(解放タイミング)を意識することが大切です。
__init__の基本の書き方
シグネチャとselfの位置
__init__
の第1引数はself
です。
これは「自分自身のインスタンス」を受け取る予約的な慣習名で、呼び出し側で渡す必要はありません。
class User:
def __init__(self, name, age):
# 第1引数 self はインスタンス自身
self.name = name # 呼び出し側から来た name を属性に保存
self.age = age
# 呼び出し側は self を渡さない
u = User("Alice", 20)
print(u.name, u.age)
Alice 20
よくある誤り
次のようにself
を明示的に渡す必要はありません。
渡すとTypeError
になります。
class Bad:
def __init__(self, x):
self.x = x
# 誤り: self を渡してしまう
# b = Bad(b, 10) # ← こう書かない
属性の初期設定とインスタンスの状態管理
属性はself.xxx
に代入することで定義されます。
利用側がいつ参照しても良いように、すべての分岐で必要な属性が必ず設定されるようにします。
class Person:
def __init__(self, name, age):
# 必須属性を必ず設定
self.name = name
self.age = age
# 状態フラグを明示しておくと後で分かりやすい
self.is_adult = age >= 18
p = Person("Bob", 16)
print(p.name, p.age, p.is_adult)
Bob 16 False
デフォルト引数とオプション引数の設計
一部の引数を省略可能にする場合はデフォルト値を使います。
関連する引数は整合性が崩れないようにまとめて設計します。
class Profile:
def __init__(self, name, nickname=None, visible=True):
self.name = name
# ニックネームが無ければ本名を見せる方針
self.nickname = nickname or name
self.visible = visible
p1 = Profile("Carol")
p2 = Profile("Dave", nickname="D")
print(p1.nickname, p1.visible)
print(p2.nickname, p2.visible)
Carol True
D True
型ヒントで読みやすさと保守性を向上
型ヒントを付けると、エディタの補完や静的解析(myPyなど)が助けになります。
実行時の動作は変わりませんが、仕様が明確になります。
from typing import Optional
class Account:
def __init__(self, username: str, plan: str = "free", email: Optional[str] = None) -> None:
# -> None は __init__ が値を返さないことの明示
self.username: str = username
self.plan: str = plan
self.email: Optional[str] = email
a = Account("eve", plan="pro")
print(a.username, a.plan, a.email)
eve pro None
実用的な初期化パターン
入力検証と例外の扱い
__init__
では不正な値を早期に弾くと、後続処理でのバグを防げます。
想定外の値にはValueError
やTypeError
を投げます。
class Temperature:
def __init__(self, celsius):
# 型チェック(必要なら厳格に)
if not isinstance(celsius, (int, float)):
raise TypeError("celsius は数値である必要があります")
# 現実的でない値を制限する例
if celsius < -273.15:
raise ValueError("絶対零度未満は指定できません")
self.celsius = float(celsius)
t = Temperature(25)
print(t.celsius)
# 例外が出る例
try:
Temperature("hot")
except Exception as e:
print(type(e).__name__, ":", e)
25.0
TypeError : celsius は数値である必要があります
計算済み値やリソースの準備
頻繁に利用する派生値を__init__
で計算しておくと、その後の処理が簡潔になります。
重い前処理や正規表現のコンパイルなども有効です。
import math
import re
class Circle:
def __init__(self, radius):
if radius <= 0:
raise ValueError("radius は正の数である必要があります")
self.radius = float(radius)
# 面積はよく使うので計算しておく
self.area = math.pi * self.radius ** 2
class LogFilter:
def __init__(self, pattern):
# 正規表現を事前にコンパイルして高速化
self._regex = re.compile(pattern)
def match(self, line):
return bool(self._regex.search(line))
c = Circle(2)
print(round(c.area, 3))
f = LogFilter(r"ERROR|WARN")
print(f.match("INFO: ok"))
print(f.match("ERROR: failed"))
12.566
False
True
可変デフォルト引数の落とし穴(list, dict)
デフォルト引数は関数定義時に1回だけ評価されるため、list
やdict
をデフォルトにするとインスタンス間で共有されてしまいます。
None
を使って中で新しいオブジェクトを作るのが定石です。
# 悪い例: インスタンス間で同じリストが共有される
class BadBag:
def __init__(self, items=[]): # ← 危険
self.items = items
# 良い例: None を番兵値として使い、その都度新しいリストを作る
class GoodBag:
def __init__(self, items=None):
self.items = list(items) if items is not None else []
b1 = BadBag()
b2 = BadBag()
b1.items.append("x")
print("Bad:", b1.items, b2.items) # 共有されてしまう
g1 = GoodBag()
g2 = GoodBag()
g1.items.append("x")
print("Good:", g1.items, g2.items) # 独立している
Bad: ['x'] ['x']
Good: ['x'] []
__init__は値を返さない(return不要)
__init__
で値を返してはいけません。
return
を書かなくても暗黙にNone
が返ります。
値を返すとTypeError
になります。
class Wrong:
def __init__(self):
# return 123 # ← これを有効化するとエラー
pass
try:
class Wrong2:
def __init__(self):
return 123 # 明示的に値を返すのはエラー
w = Wrong2()
except Exception as e:
print(type(e).__name__, ":", e)
TypeError : __init__() should return None, not 'int'
よくあるエラーと対処法
TypeError: init() missing の原因と解決
必須引数を渡し忘れると発生します。
__init__
のシグネチャを確認し、必要な引数をすべて渡してください。
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
try:
p = Point(10) # y を渡し忘れ
except Exception as e:
print(type(e).__name__, ":", e)
# 正しくは:
p2 = Point(10, 20)
print(p2.x, p2.y)
TypeError : Point.__init__() missing 1 required positional argument: 'y'
10 20
AttributeErrorが出る初期化漏れの見直し
分岐により一部の属性が設定されないとAttributeError
になります。
すべての経路で属性が定義されるように、初期値を先に入れておくと安全です。
class Config:
def __init__(self, mode):
# 先にデフォルトを入れる
self.debug = False
if mode == "dev":
self.debug = True
elif mode == "prod":
pass
else:
raise ValueError("unknown mode")
c = Config("dev")
print(c.debug)
try:
c2 = Config("prod")
print(c2.debug) # すべての経路で debug が存在
except Exception as e:
print(type(e).__name__, ":", e)
True
False
初期値を用意せず、特定の分岐だけで属性を設定していると、別の分岐で存在せずに落ちることがあります。
常に「全経路で属性が存在するか」を確認します。
初期化順序のバグを避けるチェックポイント
ある属性が別の属性に依存する場合は、依存先を先に設定します。
順序が逆だと未初期化の値を参照してしまいます。
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
# 依存する計算は最後に
self.area = self.width * self.height
r = Rectangle(3, 4)
print(r.area)
# 悪い例(参考): area を先に計算してしまうと width/height が未設定の可能性
# class BadRectangle:
# def __init__(self, width, height):
# self.area = self.width * self.height # ← ここで AttributeError
# self.width = width
# self.height = height
12
初期化時は、次の観点を順に満たすように組み立てると安全です。
まず必須の生データ、次に整合性チェック、最後にそれに依存する派生値や外部リソースの準備、という順序を意識するとバグを防げます。
まとめ
__init__
はインスタンスの「最小完全状態」を作るための初期化メソッドです。
第1引数はself
、戻り値はNone
、必要な属性は全経路で必ず設定し、入力検証を通して不正値を早期に排除することが基本方針です。
可変デフォルト引数の共有や初期化順の逆転といった典型的な落とし穴を避けることで、堅牢で読みやすいクラス設計に近づきます。
型ヒントや計算済み値の準備も活用しながら、日々のコードで__init__
を正しく使いこなしていきましょう。