Pythonでクラスを書けるようになった次の関門が継承(inheritance)です。「人間クラス」を土台にして「魔法使い」「剣士」を派生させる――そんなRPGっぽい設計ができるようになると、コードの重複が一気に消え、ファイル全体が驚くほどスッキリ整っていきます🎮
逆にここを曖昧にしたまま大規模なコードを書き続けると、似たような処理を何度もコピペしてしまい、修正のたびに10ヶ所直して回るハメに……。今日のうちに継承の考え方を腹落ちさせておけば、明日からの開発スピードが体感で2倍変わってきます🚀
継承を一言で表すと、基底クラス(親)の性質を派生クラス(子)が引き継ぐ仕組みです。呼び方はいろいろあるので、最初に整理しておきましょう。
言葉が複数あるだけで、指しているものは同じ。記事や書籍によって表記が揺れるので、「ぜんぶ同じ」と認識しておけば混乱しません🧠
たとえばゲームを作っていて、こんなクラスを用意したとします。
「防御する」はどの職業にも必ずある共通動作。これを職業ごとのクラスに毎回書いていたら、修正のたびに全クラスを直して回る悪夢が待っています😱
そこで「人間クラス(Human)」を基底クラスとして作り、「防御する」だけを書いておきます。魔法使いクラスと剣士クラスは人間クラスを継承するだけで、自動的に「防御する」を持った状態になる――これが継承の威力です✨
構文はとてもシンプル。派生クラスのクラス名のあとに丸カッコで基底クラスを書くだけです。
class Human:
def defend(self):
print("防御しました")class Wizard(Human): # ← Humanを継承
passw = Wizard()
w.defend() # → 防御しました
Wizardクラス自体には何も書いていないのに、defend()がちゃんと動きます。これは基底クラスHumanのメソッドを継承しているからです。インスタンス変数やクラス変数も同じように引き継げるので、self.hpのような属性にも自然にアクセスできます🎯
派生クラスで__init__(イニシャライザ)を独自に定義する場合、親クラスの初期化処理は明示的に呼び出す必要があります。ここで登場するのがsuper()です。
class Human:
def __init__(self):
print("Human init")
self.hp = 200class Wizard(Human):
def __init__(self):
super().__init__() # ← 親の__init__を呼ぶ
self.mp = 50
super().__init__()を呼ぶことで、親が用意してくれていたself.hp = 200などの初期化処理がきちんと走ります。これを忘れると親の属性が空っぽのまま生まれてしまうので注意⚠️
ちなみに、派生クラスに__init__をまったく書かなければ、インスタンス生成時に自動で親の__init__が呼ばれます。「親の初期化を上書きしたくない時はあえて書かない」のもアリです。
基底クラスに書かれているメソッドを派生クラスで「上書き」することをオーバーライドと呼びます。やり方は単純で、同じ名前のメソッドを派生クラスにもう一度定義するだけ。
class Human:
def defend(self):
print("普通に防御")class Wizard(Human):
def defend(self):
print("魔法バリアで防御!")Wizard().defend() # → 魔法バリアで防御!
派生クラスに同名のメソッドがあれば、そちらが優先的に呼ばれます。「基本動作は親に任せて、職業ごとの個性だけ子クラスで書く」という設計ができるので、コードの責任範囲がきれいに分かれます🎨
Pythonは多重継承もサポートしています。たとえば「魔法使いでもあり、剣士でもある」魔法剣士クラスを作るなら、丸カッコにカンマ区切りで複数の基底クラスを書きます。
class MagicSwordFighter(Wizard, SwordFighter):
pass
同じ名前のメソッドや変数が複数の親に存在する場合、先に書いた基底クラスが優先されます(上の例ならWizard)。super()もWizardを指す形になります。
ただし、多重継承は便利な反面、頭の中が一気にこんがらがる仕組みでもあります。実務では「Mixin(ミックスイン)」と呼ばれる、インスタンス変数を持たず他クラスと組み合わせて使う設計パターンとしてよく登場します。多用は禁物、ピンポイントで使うのが鉄則です🧩
近年のオブジェクト指向設計では、「継承よりコンポジション(合成)」が推奨される場面も増えています。継承は強力ですが、親を変更すると子に一気に影響が及ぶため、設計が硬直しやすいのが弱点です。
@dataclassでデータ格納用のクラスを簡潔に書ける「継承ありき」で設計を始めるのではなく、「これは本当に親子関係か?」と一度立ち止まる癖をつけると、後々の改修コストが激減します🛡
継承は概念が抽象的なぶん、良質な教材+手を動かす環境が揃っているかで習得スピードが大きく変わります。学習効率を底上げしてくれる厳選アイテムをまとめました📚
クラスの基本から継承・特殊メソッド・データクラスまで、図解と具体例で丁寧に進む定番書。独学者の最初の一冊として相性抜群です。
super()の正しい使い方、MROの考え方、データクラスの活用など、ワンランク上のクラス設計を体系的に学べます。読むたびに自分のコードが洗練されていく感覚が楽しい一冊。
「継承を使うべきか、コンポジションにすべきか」を判断する力は、言語を超えた設計の知識が支えます。長く役立つ一冊です。
左に教材、右にエディタとPython REPL。この配置に慣れると、クラスの挙動を実験しながら学ぶスピードが体感2倍になります。
クラス設計は手を動かしながら掴むのが一番。打鍵感の良いキーボードに替えると、写経や検証コードを書くハードルがぐっと下がります🎹
判断基準のひとつが「is-a(〜である)」と「has-a(〜を持つ)」の違いです。「魔法使いは人間である」のように本質的な親子関係なら継承、「車はエンジンを持つ」のように構成要素の関係なら別クラスを内部に持つコンポジションが向いています。迷ったらコンポジション寄りで考えると失敗しにくいです。
親クラスの__init__に書かれていた初期化処理(属性のセットなど)が一切走りません。後からself.hpにアクセスしようとしてAttributeErrorが出る、というのが典型的なバグです。派生クラスで__init__を独自定義したら、ほぼ反射的にsuper().__init__()を書く癖をつけておくと安全です。
派生クラスのメソッド内でsuper().メソッド名()を呼べばOKです。「親の防御処理+子クラス独自の追加効果」のように、機能を拡張する形で書けます。完全に置き換えたいときはsuper()を呼ばなければよく、用途に応じて使い分けます。
使えますが、慎重に。同じ名前のメソッドや属性が複数の親に存在すると、解決順序(MRO)の理解が必要になり、バグの原因になりがちです。実務ではMixin的な使い方(インスタンス変数を持たず機能だけを提供するクラス)に限定するのが安全です。
Python 3.7以降は@dataclassを使うと、データ格納用のクラスをはるかに簡潔に書けます。__init__や__repr__を自動生成してくれるので、単なるデータの入れ物として継承を多用する前に、まずdataclassを検討する価値があります。
継承は、共通の振る舞いを基底クラスにまとめ、個別の特徴を派生クラスに集約するための強力な道具です。super()でしっかり親を呼び、必要に応じてオーバーライドで個性を加える――この基本動作が身につけば、クラスを使ったコードは驚くほど整然と書けるようになります🎼
同時に、「本当に継承で表現すべき関係か?」と問い直す視点も忘れずに。継承とコンポジションを使い分けられるようになったとき、あなたのコードは「動く」段階から「読みやすく拡張しやすい」段階へと一段引き上がります。良質な書籍と快適な作業環境を味方につけて、Pythonの設計力をじっくり育てていきましょう🚀