概要(オーバーライドは「親の振る舞いを、子で意図通りに差し替える」技術)
オーバーライドは、継承した親クラスのメソッドを子クラスで同じ名前で再定義して、振る舞いを変更・拡張する仕組みです。共通部分は親に任せ、違う部分だけ子で変えるのが基本。重要なのは、親の「契約」(引数や戻り値の意味)を守ること、必要に応じてsuper()で親の処理を呼び継ぐこと、そして過剰な上書きで壊れないよう責務を整理することです。
基本構文と考え方(同名メソッドの再定義と互換性の確保)
最小のオーバーライド
class Animal:
def speak(self) -> str:
return "..."
class Dog(Animal):
def speak(self) -> str:
return "ワン!"
print(Animal().speak()) # ...
print(Dog().speak()) # ワン!
Python親のメソッド名と同じ名前で定義すると、子のインスタンスでは子のメソッドが優先されます。
互換性(親の契約)を守る
親が引数や戻り値の意味を定めているなら、子でもそれを壊さないようにします。必要な追加は、デフォルト引数や付加情報の追加で表現します。
class PriceFormatter:
def format(self, amount: float) -> str:
return f"{amount:.2f}"
class JpyPriceFormatter(PriceFormatter):
def format(self, amount: float) -> str:
base = super().format(amount) # 親の仕様(丸め方)を活かす
return f"{base} JPY"
Pythonsuper()で「親の処理を活かして差分を足す」
前処理→親→後処理の型
class BaseService:
def process(self, payload: dict) -> dict:
if "id" not in payload:
raise ValueError("id必須")
return {"id": payload["id"], "ok": True}
class LoggingService(BaseService):
def process(self, payload: dict) -> dict:
print("[start]", payload) # 前処理(ログ)
base = super().process(payload) # 親の検証・基本整形
base["logged"] = True # 後処理(付加情報)
print("[done]", base)
return base
Python親の「正しさ」を活かしつつ、子で付加機能を重ねます。この順序で書くと壊れにくく読みやすいです。
初期化でも親の準備を呼ぶ
class ClientBase:
def __init__(self, api_key: str, timeout: float = 5.0):
import requests
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({"Authorization": f"Bearer {api_key}"})
class EUClient(ClientBase):
def __init__(self, api_key: str, timeout: float = 7.0):
super().__init__(api_key, timeout) # 親のセッション準備
self.region = "EU"
Python親が用意する前提(セッション・バリデーションなど)は必ずsuper().initで呼び継いでください。
Web / APIの実例(クライアント・例外・モデルでのオーバーライド)
APIクライアントのエンドポイント差し替え
import requests
class BaseClient:
base_url = "https://api.example.com"
def __init__(self, key: str, timeout: float = 5.0):
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({"Authorization": f"Bearer {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()
class EUClient(BaseClient):
base_url = "https://eu.api.example.com" # クラス変数の上書き(設定のオーバーライド)
Python共通の処理は親に集約し、拠点別の違いはクラス属性やメソッドをオーバーライドして切り替えます。
例外の意味を統一する「翻訳」オーバーライド
class AppError(Exception): ...
class ServiceError(AppError): ...
class UserService:
def __init__(self, client: BaseClient):
self.client = client
def profile(self, uid: str) -> dict:
try:
data = self.client.get_user(uid)
return {"id": data.get("id", ""), "name": data.get("name") or "unknown"}
except Exception as e:
raise ServiceError("ユーザー取得失敗") from e
Python外部の生例外は、ドメイン例外へ置き換えて上位へ伝えると扱いが一貫します(これも広義の“振る舞いの上書き”)。
モデルの出力表現を拡張
class UserModel:
def __init__(self, uid: str, name: str | None = None):
self.uid = uid; self.name = name or "unknown"
def to_dict(self) -> dict:
return {"id": self.uid, "name": self.name}
class AdminModel(UserModel):
def __init__(self, uid: str, name: str | None = None, role: str = "admin"):
super().__init__(uid, name)
self.role = role
def to_dict(self) -> dict:
d = super().to_dict() # 親の表現を活かす
d["role"] = self.role
return d
Python親の出力を尊重し、差分だけを加えるのが安全です。
特殊メソッドのオーバーライド(表示・比較・呼び出し)
str/reprで表示を整える
class Price:
def __init__(self, amount: float, currency: str = "JPY"):
self.amount = amount; self.currency = currency
def __str__(self) -> str: # 人向け表示
return f"{self.amount:.2f} {self.currency}"
def __repr__(self) -> str: # 開発者向け詳細
return f"Price(amount={self.amount!r}, currency={self.currency!r})"
Pythoneqで意味のある比較にする
class Price:
def __init__(self, amount: float, currency: str = "JPY"):
self.amount = amount; self.currency = currency
def __eq__(self, other: object) -> bool:
return isinstance(other, Price) and (self.amount, self.currency) == (other.amount, other.currency)
Python比較の意味を定義すると、テストや集合操作が扱いやすくなります。
重要ポイントの深掘り(責務の線引き・テスト・過剰な上書きの回避)
いつオーバーライドし、いつ合成にするか
- 「is-a(〜である)」ならオーバーライドで振る舞いを差し替える。
- 「has-a(〜を持つ)」なら別クラスを持たせる合成を優先。ログ・キャッシュ・リトライなどはミックスインか合成が壊れにくい。
テストしやすい形に分解する
- I/O(外部通信)とロジック(整形・検証)を分け、ロジック側のオーバーライドはモック無しでテスト可能にする。
- 親の契約を固定したテスト(共通ケース)と、子の差分テスト(拡張部分)を用意すると安心。
過剰な上書きはバグの温床
- 引数の意味や戻り値の型を変える上書きは避ける(互換性を壊す)。
- 親を完全に無視するより、super()で既存の正しさを活かす方が安全。
例題(割引計算をオーバーライドで柔軟に切り替える)
親に共通ロジック、子で差分だけ上書き
class Discount:
def rate(self) -> float:
return 0.05 # 基本割引5%
def apply(self, price: float) -> float:
return price * (1 - self.rate())
class PremiumDiscount(Discount):
def rate(self) -> float:
return 0.15 # プレミアムは15%
class CampaignDiscount(Discount):
def __init__(self, extra: float = 0.05):
self.extra = extra
def rate(self) -> float:
return min(0.05 + self.extra, 0.3) # 上限30%
print(Discount().apply(1000)) # 950.0
print(PremiumDiscount().apply(1000)) # 850.0
print(CampaignDiscount(0.1).apply(1000)) # 850.0
Python「共通の計算式は親」「差分の割引率は子」で分けると、拡張・テストが簡単になります。
まとめ(親の仕様を尊重し、子で“差分だけ”を洗練させる)
オーバーライドは、親クラスのメソッドを子で再定義して振る舞いを変える技術です。親の契約(引数・戻り値・前提)を守り、必要ならsuper()で親の処理を活かしながら差分を足す。Web/APIではクライアント設定・例外翻訳・モデル出力などで効果的に使える。合成との使い分けを意識し、過剰な上書きで互換性を壊さない。これを型として徹底すれば、初心者でも「読みやすく、壊れにくく、拡張しやすい」設計が自然にできるようになります。
