概要(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の基礎を身につけられます。

