Python | OOP:class 定義

Python Python
スポンサーリンク
  1. 概要(classは「データ+振る舞い」をひとまとめにする設計図)
  2. 基本構文と最短の型(init・self・属性とメソッド)
    1. 最小のクラス定義とインスタンス化
    2. インスタンス属性とクラス属性の違い
  3. Web / API で使う現実的な型(モデル・クライアント・サービス)
    1. データモデルをクラスで表す(検証は最小に)
    2. APIクライアントをクラスでまとめる(設定・再利用)
    3. サービス層での役割分担(ドメインロジックを集約)
  4. プロパティ・バリデーション・表示(使いやすいインターフェース)
    1. @propertyで「読み/計算」を自然に
    2. str / repr で出力を整える
  5. 継承・合成の基礎(コード再利用の考え方)
    1. 継承で振る舞いを拡張(superで親の初期化)
    2. 合成(組み合わせ)で責務分割
  6. 便利トピック(dataclass・型ヒント・イミュータブル)
    1. dataclassでボイラープレート削減
    2. 型ヒントで誤りを事前に減らす
    3. 不変にしたいならfrozen dataclassやプロパティのみ公開
  7. よくあるつまずきと回避(self・共有・責務の線引き)
    1. selfを書き忘れる
    2. クラス属性を意図せず共有してしまう
    3. なんでも継承で解決しようとする
    4. initで重い処理をする
  8. 例題(WebミニアプリのOOP設計をゼロから)
    1. 設定→クライアント→サービス→エンドポイントの流れ
  9. まとめ(「設計図」を正しく描き、責務で分け、使いやすい面を整える)

概要(classは「データ+振る舞い」をひとまとめにする設計図)

クラスは、関連するデータ(属性)と処理(メソッド)をまとめる「設計図」です。設計図から作る具体物がインスタンス。初心者がまず身につけるべきは、initでの初期化、selfの意味、インスタンス属性とクラス属性の違い、メソッド定義、そして継承・プロパティ・特殊メソッド(strなど)の基礎です。


基本構文と最短の型(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}歳)です!"

p = Person("太郎", 23)
print(p.greet())
Python
  • self: 「このインスタンス自身」を指す第1引数。呼び出し側は渡さなくてよい(Pythonが自動で渡す)。
  • init: インスタンス生成直後に呼ばれる初期化メソッド。渡された引数で属性をセットするのが基本。

インスタンス属性とクラス属性の違い

class Counter:
    unit = "回"  # クラス属性(全インスタンスで共有)
    def __init__(self):
        self.n = 0  # インスタンス属性(個別)

    def inc(self):
        self.n += 1

c1, c2 = Counter(), Counter()
c1.inc(); c2.inc(); c2.inc()
print(c1.n, c2.n, Counter.unit)  # 1 2 回
Python
  • インスタンス属性: 個体ごとに異なる値。
  • クラス属性: すべてで共有したい定数や設定に向く。

Web / API で使う現実的な型(モデル・クライアント・サービス)

データモデルをクラスで表す(検証は最小に)

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"

    def to_dict(self) -> dict:
        return {"id": self.user_id, "name": self.name}
Python
  • 狙い: APIレスポンスやDB行を「扱いやすい型」に整える。外界の検証は早めに。

APIクライアントをクラスでまとめる(設定・再利用)

import requests

class ApiClient:
    def __init__(self, base_url: str, api_key: str, timeout: float = 5.0):
        if not base_url.startswith("https://"):
            raise ValueError("HTTPSのみ許可")
        self.base_url = base_url
        self.api_key = api_key
        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
  • 効果: ベースURLやヘッダーを1箇所で管理。複数メソッドから再利用でき、テストでモック化もしやすい。

サービス層での役割分担(ドメインロジックを集約)

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

    def profile(self, uid: str) -> dict:
        data = self.client.get_user(uid)
        user = User(data.get("id", ""), data.get("name"))
        return user.to_dict()
Python
  • 狙い: 通信(クライアント)とドメイン整形(サービス)を分けるとテストが楽。

プロパティ・バリデーション・表示(使いやすいインターフェース)

@propertyで「読み/計算」を自然に

class Rectangle:
    def __init__(self, w: float, h: float):
        if w <= 0 or h <= 0:
            raise ValueError("サイズは正の数")
        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

r = Rectangle(2, 3)
print(r.area)  # 6
Python
  • ポイント: getter/setterで不変条件を守る。計算結果(面積)をプロパティ化すると使いやすい。

str / repr で出力を整える

class User:
    def __init__(self, user_id: str, name: str):
        self.user_id = user_id
        self.name = name

    def __str__(self) -> str:
        return f"User({self.user_id}, {self.name})"  # 人向け

    def __repr__(self) -> str:
        return f"User(user_id={self.user_id!r}, name={self.name!r})"  # 開発者向け
Python
  • 指針: strはログやUI、reprはデバッグ・インタラクティブシェルで読みやすく。

継承・合成の基礎(コード再利用の考え方)

継承で振る舞いを拡張(superで親の初期化)

class Animal:
    def __init__(self, name: str):
        self.name = name
    def speak(self) -> str:
        return "..."

class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name)  # 親の初期化
        self.breed = breed
    def speak(self) -> str:
        return "ワン!"

d = Dog("ポチ", "柴")
print(d.name, d.speak())
Python
  • 指針: 「is-a」関係なら継承。無理に継承せず、別クラスを持たせる「has-a(合成)」の方が壊れにくい場面も多い。

合成(組み合わせ)で責務分割

class Logger:
    def log(self, msg: str):
        print(msg)

class Service:
    def __init__(self, logger: Logger):
        self.logger = logger
    def run(self):
        self.logger.log("start")
Python
  • 効果: クラスの入れ替え(テストダブル・拡張)が簡単。

便利トピック(dataclass・型ヒント・イミュータブル)

dataclassでボイラープレート削減

from dataclasses import dataclass

@dataclass
class UserDTO:
    id: str
    name: str | None = None

u = UserDTO("123", "taro")
print(u)  # UserDTO(id='123', name='taro')
Python
  • 利点: init/repr/比較などを自動生成。学習初期のデータ容器に最適。

型ヒントで誤りを事前に減らす

def fetch(uid: str) -> dict: ...
Python
  • 効果: エディタ補完や静的チェックが効き、関数・メソッドの契約が明確になる。

不変にしたいならfrozen dataclassやプロパティのみ公開

  • 狙い: 外部から勝手に書き換えられない設計にし、状態の整合性を守る。

よくあるつまずきと回避(self・共有・責務の線引き)

selfを書き忘れる

  • 症状: メソッド定義や呼び出しでTypeError(引数数が合わない)。
  • 対策: メソッドの第1引数は必ずself。クラス外から呼ぶときにselfは渡さない。

クラス属性を意図せず共有してしまう

  • 症状: 全インスタンスで値が連動して変わる。
  • 対策: 可変データ(list/dict)はインスタンス属性(self.xxx)に。共有したいのは定数だけ。

なんでも継承で解決しようとする

  • 症状: 階層が複雑化し、変更が難しくなる。
  • 対策: 「is-a」なら継承、「has-a」なら合成。まず合成を検討する。

initで重い処理をする

  • 症状: 生成だけでネットワーク・ファイルI/Oが走り、テストが遅く壊れやすい。
  • 対策: 初期化は軽く、I/Oは明示的メソッドで行う(connect、loadなど)。

例題(WebミニアプリのOOP設計をゼロから)

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

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

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

    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}")
        return User(data["id"], data.get("name")).to_dict()
Python
  • 効果: 責務が分離されテスト可能。設定注入→クライアント→サービスの順で依存が明確。

まとめ(「設計図」を正しく描き、責務で分け、使いやすい面を整える)

classは、データと振る舞いをひとまとまりにする設計図です。initとselfでインスタンスを正しく初期化し、インスタンス属性とクラス属性を使い分ける。Web/APIでは「モデル」「クライアント」「サービス」を分けて現実的に設計し、@propertyやstrで使いやすい面(インターフェース)を整える。再利用は継承より合成を優先し、dataclassや型ヒントで実用性を底上げする。これだけで、初心者でも短いコードで「読みやすく、壊れにくく、拡張しやすい」OOPの基礎を身につけられます。

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