Python | OOP:インスタンス変数

Python Python
スポンサーリンク

概要(インスタンス変数は「各オブジェクト固有の状態」)

インスタンス変数は、クラスから作られた各インスタンスが個別に持つデータです。同じクラスでも、インスタンスごとに値が違ってよい(むしろ違うのが普通)というのがポイントです。定義は通常、init内でself.xxxに代入します。クラス変数(全体共有)と混同しないこと、可変データを安全に初期化すること、更新の入口を決めることが重要です。


基本の作り方(initとselfで状態を持たせる)

最小例と動きの確認

class Person:
    def __init__(self, name: str, age: int):
        self.name = name   # インスタンス変数
        self.age = age     # インスタンス変数

    def greet(self) -> str:
        return f"こんにちは、{self.name}{self.age}歳)です!"

p1 = Person("太郎", 20)
p2 = Person("花子", 25)
print(p1.greet())  # 太郎の情報
print(p2.greet())  # 花子の情報(p1とは別の状態)
Python

この例のnameとageが、各インスタンスに固有のインスタンス変数です。p1の変更はp2に影響しません。

インスタンス変数の更新と参照

p1.age += 1
print(p1.age)  # 21
print(p2.age)  # 25(影響なし)
Python

「その人の年齢」というコンテキストに沿って、個別に更新されます。


重要ポイントの深掘り(クラス変数との違い・可変の安全化・契約)

クラス変数との違い(共有か個別か)

クラス変数は全インスタンスで共有される設定・定数に向きます。インスタンス変数は個別の状態に向きます。可変データ(list/dict)は必ずインスタンス変数にして、混線を防ぎます。

class Counter:
    unit = "回"            # クラス変数(共有)
    def __init__(self):
        self.n = 0         # インスタンス変数(個別)
Python

可変データの初期化(デフォルト引数の落とし穴回避)

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

# 良い例:Noneを受けて新規生成する
class Bag:
    def __init__(self, items: list | None = None):
        self.items = [] if items is None else list(items)
Python

インスタンスごとに別々のリスト/辞書を持たせるのが安全です。

契約(前提)の明示で壊れない初期化にする

class User:
    def __init__(self, user_id: str, name: str | None = None):
        if not user_id:
            raise ValueError("user_idは必須です")
        self.user_id = user_id
        self.name = name or "unknown"
Python

入口で前提をチェックしてからインスタンス変数へ格納すると、後段のロジックが安定します。


アクセスの設計(公開面・内部面・プロパティ)

内部用はアンダースコア、公開は@propertyで守る

class Rectangle:
    def __init__(self, w: float, h: float):
        self._w = w
        self._h = h

    @property
    def width(self) -> float:
        return self._w

    @width.setter
    def width(self, v: float):
        if v <= 0:
            raise ValueError("widthは正の数")
        self._w = v

    @property
    def area(self) -> float:
        return self._w * self._h
Python

直接書き換えると壊れやすい属性は内部用(_name)に置き、公開面は@property経由で検証付きにします。

更新の入口を用意して整合性を維持

class Profile:
    def __init__(self, name: str):
        self._name = name

    def rename(self, new_name: str):
        if not new_name.strip():
            raise ValueError("名前は空不可")
        self._name = new_name
Python

一箇所にルールを集約しておくと、状態の整合性が保てます。


仕組み理解(属性探索・dict・slots)

属性探索(インスタンス→クラス→親クラス)

アクセスはまずインスタンスのdict(個別の状態)を探し、なければクラスや親へ進みます。インスタンスに同名があればそれが優先されます。

class Config:
    timeout = 5.0  # クラス側のデフォルト

c = Config()
print(c.timeout)  # 5.0(クラスの値)
c.timeout = 10.0  # インスタンス側に上書き(インスタンス変数ができる)
print(c.timeout)  # 10.0
Python

dictで中身を覗く

class A:
    def __init__(self):
        self.x = 1
a = A()
print(a.__dict__)  # {'x': 1}
Python

どんなインスタンス変数があるか確認できます。

slotsで属性を固定して軽量化

大量生成する小さなオブジェクトは、許可する属性名を固定してメモリ削減・タイプミス防止が可能です。

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

Web / APIでの実践(モデル・セッション・設定の扱い)

モデルで揺れるレスポンスを吸収して属性化

class UserModel:
    def __init__(self, data: dict):
        self.user_id = str(data.get("id", ""))
        self.name = data.get("name") or "unknown"

    def to_dict(self) -> dict:
        return {"id": self.user_id, "name": self.name}
Python

インスタンス変数へ最小保証の形で格納すると、後続処理がシンプルになります。

クライアントで再利用する接続を属性に持つ

import requests

class ApiClient:
    def __init__(self, base_url: str, api_key: str, timeout: float = 5.0):
        self.base_url = base_url
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {api_key}"})

    def get_user(self, uid: str) -> dict:
        r = self.session.get(f"{self.base_url}/users/{uid}", timeout=self.timeout)
        r.raise_for_status()
        return r.json()
Python

セッションやタイムアウトはインスタンス変数に持ち、各メソッドから再利用します。

設定の不変化と安全な公開

class Settings:
    def __init__(self, base_url: str, api_key: str):
        self._base_url = base_url
        self._api_key = api_key

    @property
    def base_url(self) -> str:
        return self._base_url

    @property
    def api_key(self) -> str:
        return self._api_key
Python

外から安易に書き換えられないように、不変寄せで公開します。


例題(インスタンス変数設計で小さなAPIラッパーを整える)

設定→クライアント→サービスの流れ

from dataclasses import dataclass
import requests

@dataclass(frozen=True)
class Settings:
    base_url: str
    api_key: str

class Client:
    def __init__(self, s: Settings, timeout: float = 5.0):
        self.base_url = s.base_url
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {s.api_key}"})

    def get(self, path: str) -> dict:
        r = self.session.get(f"{self.base_url}{path}", timeout=self.timeout)
        r.raise_for_status()
        return r.json()

class UserService:
    def __init__(self, client: Client):
        self.client = client

    def user(self, uid: str) -> dict:
        data = self.client.get(f"/users/{uid}")
        u = UserModel(data)
        return u.to_dict()
Python

各クラスがインスタンス変数で必要な状態を持ち、責務が分離されるため、テストと拡張が楽になります。


まとめ(「個別の状態はインスタンス変数」で一貫性を保つ)

インスタンス変数は各オブジェクト固有の状態を表し、initでself.xxxに格納して初期化します。クラス変数(共有)と混同せず、可変データは必ずインスタンス変数へ。公開面は@propertyで守り、更新の入口を用意して整合性を維持する。属性探索やdict/slotsの仕組みを理解して、動作を予測可能にする。これを型として徹底すれば、初心者でも「壊れない・読みやすい」状態管理が自然にできるようになります。

タイトルとURLをコピーしました