閉じる

【Python】__init()__の基本: オブジェクト生成時の初期設定を解説

オブジェクト指向でクラスを使い始めると、インスタンス生成時にどの値を持たせるかが重要になります。

その初期設定を担うのが__init__()です。

本記事では、__init__の役割や書き方、実用的な初期化パターン、よくあるエラーと解決策まで、初心者の方が段階的に理解できるよう丁寧に解説します。

Pythonのコンストラクタ(init)の役割

__init__の意味と動作タイミング

__init__はインスタンス生成直後に自動的に呼び出される初期化メソッドです。

厳密には「コンストラクタ(インスタンス生成)」は__new__で、__init__は生成済みのインスタンスに初期値を設定する「イニシャライザ」です。

戻り値は常にNoneである必要があり、値を返しません。

次のコードは、__new____init__の呼び出し順を確認するものです。

Python
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__が呼ばれ、属性の設定や検証などの初期化処理が実施されます。

ここで例外を投げると初期化は中断され、インスタンスは完成しません。

正常に終了すると、アプリケーションから利用可能な完成済みのオブジェクトが返されます。

以下の表は要点をまとめたものです。

ステップ説明
1ClassName(...)が呼ばれる
2__new__でインスタンスの枠が作られる
3__init__で属性初期化や検証を行う
4完成したインスタンスが呼び出し元に返る

初期化で何を設定すべきか

初期化では、そのインスタンスが「不変条件(invariant)」を満たすように必要十分な情報を設定します。

具体的には、外部から受け取った引数を属性に保存し、常に正しい状態で使えるよう最低限の検証を行います。

さらに、後続処理で頻繁に使う派生値(計算済み値)を準備しておくとパフォーマンスや可読性が向上します。

必要に応じて、ファイルや接続などのリソース確保もここで行いますが、ライフサイクル(解放タイミング)を意識することが大切です。

__init__の基本の書き方

シグネチャとselfの位置

__init__の第1引数はselfです。

これは「自分自身のインスタンス」を受け取る予約的な慣習名で、呼び出し側で渡す必要はありません。

Python
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になります。

Python
class Bad:
    def __init__(self, x):
        self.x = x

# 誤り: self を渡してしまう
# b = Bad(b, 10)  # ← こう書かない

属性の初期設定とインスタンスの状態管理

属性はself.xxxに代入することで定義されます。

利用側がいつ参照しても良いように、すべての分岐で必要な属性が必ず設定されるようにします。

Python
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

デフォルト引数とオプション引数の設計

一部の引数を省略可能にする場合はデフォルト値を使います。

関連する引数は整合性が崩れないようにまとめて設計します。

Python
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など)が助けになります。

実行時の動作は変わりませんが、仕様が明確になります。

Python
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__では不正な値を早期に弾くと、後続処理でのバグを防げます。

想定外の値にはValueErrorTypeErrorを投げます。

Python
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__で計算しておくと、その後の処理が簡潔になります。

重い前処理や正規表現のコンパイルなども有効です。

Python
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回だけ評価されるため、listdictをデフォルトにするとインスタンス間で共有されてしまいます。

Noneを使って中で新しいオブジェクトを作るのが定石です。

Python
# 悪い例: インスタンス間で同じリストが共有される
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になります。

Python
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__のシグネチャを確認し、必要な引数をすべて渡してください。

Python
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になります。

すべての経路で属性が定義されるように、初期値を先に入れておくと安全です。

Python
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

初期値を用意せず、特定の分岐だけで属性を設定していると、別の分岐で存在せずに落ちることがあります。

常に「全経路で属性が存在するか」を確認します。

初期化順序のバグを避けるチェックポイント

ある属性が別の属性に依存する場合は、依存先を先に設定します。

順序が逆だと未初期化の値を参照してしまいます。

Python
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__を正しく使いこなしていきましょう。

Python 実践TIPS - クラスとオブジェクト指向
この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

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

URLをコピーしました!