閉じる

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

オブジェクトを作るたびに属性の初期値を入れたり、設定値を整えておくには、コンストラクタである__init__の使い方を理解することが近道です。

本記事では、__init__の役割、引数設計、よくある落とし穴、実践パターンまでを初心者の方にも分かるように段階的に解説します

実行できるサンプルコードと出力も順に示します。

__init__とは: オブジェクト生成と初期化の基本

__init__の役割と呼ばれるタイミング

__init__は、クラスからインスタンスが生成された直後に自動的に呼ばれる「初期化メソッド」です

インスタンスの雛形となるメモリは__new__で確保され、その直後に__init__で属性へ値を入れたり、前処理をしたりします。

以下は最小の例です。

生成のタイミングでメッセージを表示し、インスタンス変数を設定します。

Python
# __init__の呼び出しタイミングを確認する最小例

class Greeter:
    def __init__(self, name):  # nameは必須引数
        print("[__init__] called")  # いつ呼ばれるかを可視化
        self.name = name          # インスタンス変数の設定

    def say_hello(self):
        return f"Hello, {self.name}!"

g = Greeter("Alice")              # インスタンス生成と同時に__init__が呼ばれる
print(g.say_hello())
実行結果
[__init__] called
Hello, Alice!

selfの意味とインスタンス変数の設定

selfは「今まさに初期化しているインスタンス自身」を指します

self.x = ...のようにして、インスタンスごとに固有のデータを保持します。

ローカル変数に値を入れるだけではインスタンスに保存されません。

Python
# selfを付け忘れるとインスタンスに値が残らない例

class Counter:
    def __init__(self, start):
        count = start      # これはローカル変数。インスタンスに保存されない
        self.count = start # こちらが正しい。各インスタンスの状態となる

c1 = Counter(10)
# print(count)  # NameError: ローカル変数は外から参照できない
print(c1.count)  # self.countはインスタンスに保存されている
実行結果
10

インスタンスの状態として残したいデータは、必ずself.属性名へ代入します

__init__の定義とシグネチャ(引数と戻り値)

__init__(self, ...)は任意の引数を取れますが、戻り値は常にNoneでなければなりません

値を返すとTypeErrorになります。

Python
class Sample:
    def __init__(self, x: int, y: int = 0):  # 型ヒントとデフォルト値
        self.x = x
        self.y = y
        # return 123  # これはエラー: __init__() should return None

以下に、よく使う引数の種類を整理します。

種類説明
必須位置引数x常に渡す必要がある
オプション(デフォルト)y=0省略可能。指定がなければ既定値
可変長(位置)*args追加の位置引数をまとめて受け取る
可変長(キーワード)**kwargs追加のキーワード引数を辞書で受け取る
戻り値None何も返さない。返すとエラー

__new__との違い(生成と初期化の分担)

__new__はインスタンスを「生成」し、__init__は「初期化」します

通常は__init__のみを書けば十分です。

__new__はイミュータブル型(例: tuple)のサブクラス化など特殊なケースで使います。

Python
# __new__と__init__の呼び順を確認する例

class Trace:
    def __new__(cls, *args, **kwargs):
        print("[__new__] allocate instance")
        return super().__new__(cls)  # インスタンスを実際に生成

    def __init__(self, value):
        print("[__init__] initialize instance")
        self.value = value

t = Trace(42)
print(t.value)
実行結果
[__new__] allocate instance
[__init__] initialize instance
42

__init__の書き方: 引数設計とデフォルト値

必須引数とオプション引数の設計

初期化時に必ず必要な情報と、後からでも設定できる情報を分けると分かりやすくなります。

「なければオブジェクトが成立しないもの」は必須引数、「なければ既定挙動でよいもの」はオプションにします

Python
class User:
    def __init__(self, name, is_admin=False):
        # nameは必須、is_adminはオプション
        if not name:
            raise ValueError("nameは空にできません")
        self.name = name
        self.is_admin = is_admin

u1 = User("Alice")
u2 = User("Bob", is_admin=True)
print(u1.name, u1.is_admin)
print(u2.name, u2.is_admin)
実行結果
Alice False
Bob True

デフォルト引数と型ヒントの付け方

型ヒントは読みやすさとツール支援に有効です。

オプション値にはOptional[型]を使います。

Python
from typing import Optional

class Article:
    def __init__(self, title: str, body: str, tags: Optional[list[str]] = None):
        self.title = title
        self.body = body
        self.tags = tags if tags is not None else []  # 後述のミュータブル注意点にも対応

a = Article("入門", "本文です")
print(a.title, a.tags)
実行結果
入門 []

可変長引数(*args, **kwargs)の受け取り方

未知の追加オプションを受け付けたい場合、*args**kwargsで受け取って安全に保存や委譲ができます。

Python
class Configurable:
    def __init__(self, name: str, *plugins, **options):
        self.name = name
        self.plugins = list(plugins)   # 位置引数の余りをプラグイン群として扱う
        self.options = dict(options)   # キーワード引数を設定辞書として保持

c = Configurable("service", "p1", "p2", timeout=3.0, retries=2)
print(c.plugins)
print(c.options)
実行結果
['p1', 'p2']
{'timeout': 3.0, 'retries': 2}

ミュータブルなデフォルト引数の落とし穴(list dict)

ミュータブル(書き換え可能)なオブジェクトをデフォルト引数に直接書くのは危険です。

関数定義時に1回だけ作られ、インスタンス間で共有されてしまいます。

デフォルトはNoneにして中で新しく作るのが定石です。

Python
# 悪い例: リストが全インスタンスで共有される
class BagBad:
    def __init__(self, items=[]):
        self.items = items

b1 = BagBad()
b2 = BagBad()
b1.items.append("apple")
print("bad:", b1.items, b2.items)  # b2にも影響が出る

# 良い例: Noneを使って都度新しく作る
class BagGood:
    def __init__(self, items=None):
        self.items = list(items) if items is not None else []

g1 = BagGood()
g2 = BagGood()
g1.items.append("apple")
print("good:", g1.items, g2.items)  # g2には影響しない
実行結果
bad: ['apple'] ['apple']
good: ['apple'] []

初期化順序と依存関係の整理

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

必要ならヘルパーメソッドに処理を分け、例外発生時でも一部だけ中途半端に設定されないように注意します。

Python
class Rectangle:
    def __init__(self, width: float, height: float):
        self.width = self._validate_length(width)
        self.height = self._validate_length(height)
        self.area = self.width * self.height  # 依存する計算は最後に

    def _validate_length(self, v: float) -> float:
        if v <= 0:
            raise ValueError("widthとheightは正の数である必要があります")
        return float(v)

r = Rectangle(3, 4)
print(r.area)
実行結果
12.0

クラスの初期設定パターン: __init__の実践

入力値のバリデーション(ValueError TypeError)

型が違うときはTypeError、値の範囲が不正なときはValueErrorを上げるのが通例です。

Python
class Person:
    def __init__(self, name, age):
        if not isinstance(name, str):
            raise TypeError("nameはstrである必要があります")
        if not isinstance(age, int):
            raise TypeError("ageはintである必要があります")
        if age < 0:
            raise ValueError("ageは0以上である必要があります")
        self.name = name
        self.age = age

try:
    Person("Alice", -1)
except Exception as e:
    print(type(e).__name__, str(e))
実行結果
ValueError ageは0以上である必要があります

設定値の正規化と前処理(文字列整形 数値変換)

初期化中に、表記ゆれや空白、単位の違いを揃えておくと後段が楽になります。

Python
class Product:
    def __init__(self, title: str, price):
        title = title.strip()  # 前後空白の削除
        if not title:
            raise ValueError("titleは空にできません")
        self.title = title

        # 価格は文字列でも数値でも受け取り、小数に正規化
        try:
            self.price = float(price)
        except Exception as e:
            raise TypeError("priceは数値に変換可能である必要があります") from e

p = Product("  Book  ", "1200")
print(p.title, p.price)
実行結果
Book 1200.0

計算済み属性の初期化(キャッシュ 初期計算)

高コストの計算結果をキャッシュしておくと、後のメソッド呼び出しが高速になります。

必要に応じて遅延初期化と使い分けます。

Python
class SeriesStats:
    def __init__(self, values: list[float]):
        if not values:
            raise ValueError("valuesは空にできません")
        self.values = values
        # ここでは小コストな集計だけ先に計算
        self.count = len(values)
        self.sum = sum(values)
        self.mean = self.sum / self.count

s = SeriesStats([1, 2, 3, 4])
print(s.count, s.sum, s.mean)
実行結果
4 10 2.5

外部設定の受け渡し(パス 接続情報 オプション)

外部の設定値や接続情報は、そのまま保持するだけでなく、存在確認や展開、参照しやすい形に整形しておくと実運用で堅牢になります。

Python
from pathlib import Path

class DataStore:
    def __init__(self, root_dir, **options):
        self.root = Path(root_dir).expanduser().resolve()
        if not self.root.exists():
            raise ValueError(f"ディレクトリが存在しません: {self.root}")

        # 任意オプション。既定値をマージしておく
        defaults = {"readonly": False, "encoding": "utf-8"}
        merged = {**defaults, **options}
        self.readonly = merged["readonly"]
        self.encoding = merged["encoding"]

store = DataStore("./", readonly=True)
print(store.root.is_dir(), store.readonly, store.encoding)
実行結果
True True utf-8

__init__のよくあるエラーと対処法

TypeError(__init__() missing required positional argument)

必須引数を渡し忘れるとこのエラーになります。

エラーメッセージは何が欠けているかを具体的に教えてくれます

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

try:
    p = Point(10)  # yを渡し忘れ
except TypeError as e:
    print(e)

# 修正: 必須のyを渡す
p = Point(10, 20)
print(p.x, p.y)
実行結果
Point.__init__() missing 1 required positional argument: 'y'
10 20

AttributeError(初期化漏れで属性が存在しない)

インスタンス変数を設定し忘れるとAttributeErrorになります。

例外経路でも属性が未設定にならないよう、先にデフォルトを入れてから検証するのも有効です。

Python
class Session:
    def __init__(self, token: str | None):
        self.token = None  # 先に存在だけ確保しておく
        if not token:
            raise ValueError("tokenが必要です")
        self.token = token

try:
    s = Session("")  # 例外になるが、少なくとも属性は存在する設計
except ValueError as e:
    print("ValueError:", e)
実行結果
ValueError: tokenが必要です

変数のシャドーイングに注意(self.xとローカル変数)

ローカル変数と同名の属性を使うと、思わぬシャドーイングが起きます。

必ずself.を付けることを習慣化しましょう。

Python
class Shadow:
    def __init__(self, value):
        value = value  # 何も起きない。ローカルに代入しただけ
        self.value = value  # これが正しい

s = Shadow(5)
print(s.value)
実行結果
5

重い初期化の回避と遅延初期化(lazy)

初期化時に重い処理(大きなファイル読み込み、ネットワーク接続など)を行うと、オブジェクト生成が遅くなります。

必要になるまで遅らせる「遅延初期化」を検討します。

Python
class LazyLoader:
    def __init__(self, path: str):
        self.path = path
        self._data = None  # まだ読み込まない

    def get_data(self):
        if self._data is None:
            print("[load] heavy loading happens here")
            # 実際はファイルIOやネットワーク等
            self._data = f"content of {self.path}"
        return self._data

loader = LazyLoader("data.txt")
print("created")
print(loader.get_data())  # ここで初めて読み込み
print(loader.get_data())  # 2回目以降はキャッシュ利用
実行結果
created
[load] heavy loading happens here
content of data.txt
content of data.txt

まとめ

__init__は、オブジェクトの成立を保証し扱いやすくするための「初期化ポイント」です

必須とオプションの引数設計、ミュータブルなデフォルト引数を避けること、バリデーションと正規化、計算済み属性や遅延初期化の使い分けを押さえると、堅牢で読みやすいクラスになります。

分かりづらいエラーの多くはself.属性の付け忘れや引数の渡し忘れから生じます。

今回のパターンとサンプルを雛形として、自分のドメインに合わせた初期化ロジックを丁寧に組み立ててみてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

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

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

URLをコピーしました!