継承(Inheritance)
サブクラスが親クラスを継承すると,親クラスで定義されたすべてのプロパティとメソッドを引き継ぐことができます.
Definition 1 OOPにおける継承
あるクラス(親クラス,スーパークラス,基底クラス)の機能を,別のクラス(子クラス,サブクラス,派生クラス)が引き継ぐ仕組み
再利用性 : 同じような処理を複数のクラスで書かかずに,親クラスにまとめてしまう
拡張性 : 親クラスの機能をそのまま使いつつ,新しい振る舞い(メソッドや属性)を追加・変更できる
Example 1 (初期化処理 __init__ と継承)
Code
class Person:
def __init__ (self , name, nationality):
self .name = name
self .nationality = nationality
class FootballPlayer(Person):
def __init__ (self , name, nationality, position, club):
# 親クラスの初期化(nameとnationalityを設定)
super ().__init__ (name, nationality)
# 子クラス独自の属性
self .position = position
self .club = club
# インスタンス生成
player = FootballPlayer("Kylian Mbappé" , "France" , "Forward" , "Real Madrid" )
print (player.name)
print (player.nationality)
print (player.position)
print (player.club)
Kylian Mbappé
France
Forward
Real Madrid
Example 2 (dataclass を用いた初期化処理と継承)
Code
from dataclasses import dataclass
@dataclass
class Person:
name: str
nationality: str
@dataclass
class FootballPlayer(Person):
position: str
club: str = 'Arsenal' # デフォルト値OK
player = FootballPlayer("Kylian Mbappé" , "France" , "Forward" , "Real Madrid" )
print (player.name)
print (player.nationality)
print (player.position)
print (player.club)
Kylian Mbappé
France
Forward
Real Madrid
Example 3 (dataclass を用いた初期化処理と継承 with __post_init__)
dataclass は 親クラス → 子クラス の順にフィールドを結合して __init__ を自動生成
super().__init__ の設定が dataclass 継承では不要になる
親クラスでデフォルト値を用いていると少し話がややこしくなる
Code
from dataclasses import dataclass
@dataclass
class Person:
name: str
def print_name(self ):
print (self .name)
@dataclass
class MDPerson(Person):
affiliation: str
def __post_init__(self ):
self .name = f"Doctor { self . name} "
John = MDPerson('John' , 'UT' )
John.print_name()
メソッドのオーバーライド
Definition 2 メソッドのオーバーライド
親クラス(スーパークラス)で定義されたメソッドを,子クラス(サブクラス)で再定義して振る舞いを変更すること
Pythonでは,基本的にすべてのメソッドがC++における「仮想関数」に相当 = いつでもオーバーライド可能
def __methodname(self) などのようにprivate化である程度防ぐことは可能
Example 4 (親メソッドを部分的に再利用するオーバーライド)
Code
class Logger:
def log(self , msg):
print (f"[LOG] { msg} " )
class TimestampLogger(Logger):
def log(self , msg):
import datetime
now = datetime.datetime.now().isoformat()
super ().log(f" { now} - { msg} " )
TimestampLogger().log("system started" )
[LOG] 2025-11-06T18:37:45.976367 - system started
Coding Style Guide
@override デコレータ(Python 3.12以降 or typing_extensions経由)を使う, xor,docstring で「"""Override of Base.process"""」と明記する
明確な意図がない限り,基底クラスのメソッドや属性を再定義しない
実装を置き換えるのではなく拡張する場合は,super() を適切に呼び出す
基底クラス側でオーバーライドを想定していない場合は __ を付与してprivate化する
Example 5
Code
# from typing import override # Python 3.12以降利用可能)
from typing_extensions import override # Python 3.11などではtyping_extensions経由
class Base:
def process(self , data):
print ("Base processing:" , data)
class Derived(Base):
@override
def process(self , data):
"""Override of Base.process"""
print ("Derived processing:" , data)
super ().process(data)
Interface Inheritance vs Implementation Inheritance
Definition 3 Implementation Inheritance(実装継承)
既存の型を特殊化し,基底クラスのコードを再利用する継承のこと
インターフェース継承以外の継承は実装継承
Pros
基底クラスのコードを再利用するので,コード量を削減できる
継承関係はクラス定義時(コンパイル時相当)に明示されるため,静的解析ツールがその構造を理解しやすく,定義ミス(例: 未実装メソッドや誤ったオーバーライド)を早期に検出できる
Cons
基底クラスとサブクラスに実装が分散するため,クラス階層を追いかけなければ挙動を理解できない
「上書きすべきでないメソッド」を誤って上書きしてしまうリスクがある
実行時にメソッド解決順序(MRO) をたどるため,パフォーマンスが低下する可能性がある
多重継承時に,ダイヤモンド継承問題 (同一祖先を複数経路で継承する形)が発生するリスクがある
Coding Style Guide
継承階層はできるだけ浅く保ち,理想的には1〜2段階以内にとどめる
多重継承自体は許容されるが,多重実装継承は基本的には非推奨
ダイヤモンド継承は特に避けるべき
Example 6 (アンチパターン)
Code
class Device:
def power_on(self ):
print ("Power on" )
class NetworkDevice(Device):
def connect (self ):
print ("Connect to network" )
class SmartDevice(Device):
def connect (self ):
print ("Connect to app" )
# Diamond inheritance: both inherit from Device
class SmartNetworkDevice(SmartDevice, NetworkDevice):
def sync(self ):
print ("Syncing data" )
# Ambiguity: which connect() should be used?
snd = SmartNetworkDevice()
snd.connect () # -> Which one? Depends on MRO (method resolution order)
Definition 4 インターフェース継承
抽象クラスを親クラスとする継承のこと
クラスに特定のメソッド群の実装を強制することもできる継承形態
同一APIを複数クラスで統一的に提供したいときに特に利用される
「何をできるか(what)」を宣言し,「どう実装するか(how)」はサブクラスに委ねる
Example 7 (Abstract Base Classを用いたインターフェース継承)
Code
from abc import ABC, abstractmethod
class Serializer(ABC):
@abstractmethod
def serialize(self , data):
pass
class JsonSerializer(Serializer):
def serialize(self , data):
import json
return json.dumps(data)
def save_data(serializer: Serializer, data):
print (serializer.serialize(data))
# Unified interface:
save_data(JsonSerializer(), {"foo" : "bar" })
Pros
明示的なインターフェース契約により,APIの一貫性と可読性を高められる
IDE補完・型チェック・静的解析が機能する
設計段階で「実装漏れ」や「誤実装」を検出できる
ユニットテストケース準備が容易になる
Cons
小規模コードでは過剰設計になりがち(そもそも抽象クラスが必要ではない可能性がある)
抽象クラスが肥大化すると,不要なメソッド実装を強制される場合がある
API変更時に全実装クラスの修正が必要になる(保守コストの増大)
わざわざ継承を使うのではなく,ダックタイピング + コンポジションで十分なケースもある
実装継承と異なり,コード再利用による短縮効果はない
Coding Style Guide
インターフェースは必要最小限のメソッドにとどめる
共通APIを抽象クラスを用いて定義する場合は,説明用documentを用意すること
例: すべての推定器・予測モデルは BaseEstimator(ABC)を継承し,最低限 fit(X, y), predict(X) を定義し,必要に応じてscore(X, y)を追加する
Example 8 (統計推定量モジュール用の実装例)
Code
from abc import ABC, abstractmethod
import numpy as np
class BaseEstimator(ABC):
"""Abstract base class for all estimators.
Rules:
- Require fit() and predict() methods
- Optionally define score()
"""
@abstractmethod
def fit(self , X: np.ndarray, y: np.ndarray):
"""Fit model to data."""
pass
@abstractmethod
def predict(self , X: np.ndarray) -> np.ndarray:
"""Predict from model."""
pass
def score(self , X: np.ndarray, y: np.ndarray) -> float :
"""Optional: default scoring by R^2."""
y_pred = self .predict(X)
u = ((y - y_pred) ** 2 ).sum ()
v = ((y - y.mean()) ** 2 ).sum ()
return 1 - u / v
class LinearRegression(BaseEstimator):
"""Ordinary Least Squares Estimator."""
def __init__ (self ):
self .coef_ = None
self .intercept_ = None
def fit(self , X: np.ndarray, y: np.ndarray):
X_ = np.c_[np.ones(X.shape[0 ]), X]
beta = np.linalg.pinv(X_.T @ X_) @ X_.T @ y
self .intercept_ = beta[0 ]
self .coef_ = beta[1 :]
return self
def predict(self , X: np.ndarray) -> np.ndarray:
return X @ self .coef_ + self .intercept_
Then,
Code
X = np.random.rand(100 , 3 )
y = X @ np.array([1.2 , - 0.7 , 0.3 ]) + 0.5 + np.random.randn(100 ) * 0.05
model = LinearRegression()
model.fit(X, y)
print ("R^2:" , model.score(X, y))
インスタンス属性・メソッドと public / protected / private の整理
継承の文脈において,C++では「データメンバーはprivateにすべき 」とされますが,
C++ と異なり,Pythonはすべての属性とメソッドが公開が基本
親クラスの属性を完全にprivate的に運用(名前マングリング)してしまうと継承の柔軟性が大きく失われる
という理由から,private(__name) よりかはprotected(_name) という運用のほうが好ましいとされます.
公開API
name
✅
✅
利用者向けのメソッドやプロパティ
継承向け内部属性
_name
✅
⚠️(非推奨)
継承拡張で使うデータメンバー
完全に内部限定
__name
❌
❌
継承しない前提での内部実装用
Propertyの活用
API設計の観点から読み取り専用の値や派生値を返す場合,getter だけのプロパティを作成して,setter を不要にすることもできます.
Example 9
from abc import ABC, abstractmethod
class BaseEstimator(ABC):
@abstractmethod
def fit(self , X, y):
pass
@abstractmethod
def predict(self , X):
pass
class LinearEstimator(BaseEstimator):
def __init__ (self ):
self ._weights = None # protected属性として保持
@property
def weights(self ):
"""軽微な計算や検証を伴う派生値を返す"""
if self ._weights is None :
raise ValueError ("Estimator has not been fitted yet" )
return self ._weights
weights は計算や状態確認を伴うため プロパティとして提供
fit() / predict() は必須の公開API
内部状態 _weights は protected とし,直接操作させない
属性アクセスのAPIを提供する場合,プロパティは軽量な計算や制約チェックがある場合のみ使用
読み取り専用の値や派生値を返す場合,getter だけのプロパティを作成して,setter を不要にする
例外的な状況を覗いて staticmethod は使用しない
Definition 5 staticmethod
クラスに属するが,インスタンス (self) やクラス (cls) に依存しないメソッド
実際には単なる「モジュール関数」をクラス内に置いた形
Example 10
self.__class__ はインスタンスのクラスを参照するので,staticmethod も呼び出し可能
<Class>でも参照可能(CoyoteWeapon.commercial())
Code
from dataclasses import dataclass
@dataclass
class CoyoteWeapon:
name: str
power: int
@staticmethod
def commercial():
print ("This is Coyote Weapon" )
def info(self ):
self .__class__ .commercial()
print (f"Weapon: { self . name} , Power: { self . power} " )
# クラス名から staticmethod を呼び出す
CoyoteWeapon.commercial() # → This is Coyote Weapon
# インスタンスを作ってメソッドを呼ぶ
weapon = CoyoteWeapon(name= "Rocket Launcher" , power= 9000 )
weapon.info()
This is Coyote Weapon
This is Coyote Weapon
Weapon: Rocket Launcher, Power: 9000
Coding Style Guide
staticmethod の乱用を避け,モジュールレベル関数で基本対応する
MLflowなどのAPI側が実装を要求する場合のみ利用する
classmethod は限定的に利用する
Coding Style Guide
名前付きコンストラクタ や クラス固有の処理(例: プロセス全体で使うキャッシュの初期化など)でのみ使用
Example 11 (名前付きコンストラクタ)
通常のコンストラクタ __init__ とは別に,クラスを初期化する補助的なコンストラクタを作りたい場合に使う
Code
from dataclasses import dataclass
@dataclass
class Rectangle:
width: float
height: float
@classmethod
def from_square(cls, side_length: float ):
"""正方形から Rectangle を作る名前付きコンストラクタ"""
return cls(width= side_length, height= side_length)
# 使用例
r = Rectangle.from_square(5 )
print (r) # Rectangle(width=5, height=5)
Rectangle(width=5, height=5)
Example 12
_global_cache はクラス全体で共有される状態
classmethod を使うことで,インスタンスを生成せずにアクセス可能
Code
class Cache:
_global_cache = {}
@classmethod
def set (cls, key, value):
cls._global_cache[key] = value
@classmethod
def get(cls, key):
return cls._global_cache.get(key)
# 使用例
Cache.set ("x" , 100 )
print (Cache.get("x" ))
コンポジション(Composition)
継承(Inhertitance)は,「class Aは class Bの一種」という思想が背景にありますが,
アヒルは鳥であると同時にくちばしやしっぽを持っている
というようなA(アヒル)はB(くちばしやしっぽ)を有している(A-has-B logic)の場合は,コンポジション(composition)を使うべきとされます.
Example 13 (🦆 コンポジション(A-has-B logic)
Code
from dataclasses import dataclass
@dataclass
class Bill:
length_cm: float
color: str
def quack_sound(self ):
return "Quack!"
@dataclass
class Tail:
length_cm: float
fluffiness: int # 1〜10段階
def wag(self ):
return "Tail wagging gracefully"
@dataclass
class Bird:
species: str
def fly(self ):
return f" { self . species} is flying!"
@dataclass
class Duck(Bird):
bill: Bill
tail: Tail
def describe(self ):
return (
f"This is a { self . species} . \n "
f"Bill: { self . bill. length_cm} cm, color { self . bill. color} \n "
f"Tail: { self . tail. length_cm} cm, fluffiness { self . tail. fluffiness} "
)
def quack(self ):
return self .bill.quack_sound()
def wag_tail(self ):
return self .tail.wag()
duck = Duck(
species= "Mallard" ,
bill= Bill(length_cm= 6.5 , color= "yellow" ),
tail= Tail(length_cm= 8.0 , fluffiness= 7 )
)
print (duck.describe())
print (duck.fly())
print (duck.quack())
print (duck.wag_tail())
This is a Mallard.
Bill: 6.5cm, color yellow
Tail: 8.0cm, fluffiness 7
Mallard is flying!
Quack!
Tail wagging gracefully
Composition vs Inheritance
継承は慎重に使い,まずは「コンポジション(composition)」を考えるべき
「部品の入れ替え」が必要な場合や責務を分離したい」場合,Compositionが有効
関係
is-a(〜は〜である)
has-a(〜を持っている)
再利用
親クラスの機能を引き継ぐ
他クラスの機能を内部で使う
柔軟性
低い(構造が固定)
高い(入れ替えや拡張が容易)
結合度
強い
弱い(疎結合)
Appendix
ダックタイピング vs インターフェース継承
定義
「見た目がアヒルならアヒル 」の原則に基づき,オブジェクトの型ではなく振る舞い(メソッドや属性の存在) で判断する
抽象クラス(ABC)を介して,特定のメソッド群を必ず実装 することを強制する仕組み
Python 的実装例
fit() と predict() を持っていれば Estimator として扱う(Scikit-Learn 流)
Estimator 抽象基底クラスを継承し,fit()・predict() の定義を強制する