閉じる

Pythonのクラス継承入門|書き方・super・多重継承を図解で理解

Pythonで本格的なオブジェクト指向プログラミングを行うとき、避けて通れないのがクラス継承です。

継承を理解すると、コードの重複を減らし、分かりやすく拡張しやすい設計ができるようになります。

本記事では、クラス継承の基本から、super、多重継承、MROまでを図解とサンプルコードで丁寧に解説していきます。

Pythonのクラス継承とは何か

クラス継承の基本概念とメリット

クラス継承とは、あるクラス(親クラス)の機能を、そのまま引き継いだ新しいクラス(子クラス)を作る仕組みのことです。

Pythonでは、クラス定義のときに丸括弧で親クラスを書くだけで継承ができます。

継承を使う主なメリットは次の3点です。

1つ目は、コードの再利用です。

親クラスに共通の処理をまとめておくことで、複数の子クラスがその機能を共有できます。

2つ目は、拡張のしやすさです。

既存の親クラスの振る舞いを変えずに、子クラス側で機能を足したり、上書きしたりできます。

3つ目は、抽象化による整理です。

似た性質を持つクラスを階層構造でまとめることで、ドメイン(問題領域)の整理と理解がしやすくなります。

親クラス(スーパークラス)と子クラス(サブクラス)の関係

継承関係では、元になるクラスを親クラス(superclass, base class)、そこから派生するクラスを子クラス(subclass, derived class)と呼びます。

子クラスは、親クラスが持つ以下のものを引き継ぎます。

  • インスタンス変数(属性)
  • メソッド(インスタンスメソッド、クラスメソッド、静的メソッド)

ただし、子クラス側で同じ名前のメソッドを定義すれば、親クラスの実装を上書き(オーバーライド)できます。

これにより「基本的な挙動は親クラスと同じだが、一部だけ変えたい」という設計が簡単にできます。

継承とコード再利用のイメージ図解

このような図で考えると、「共通する性質は上位のクラス」「具体的な違いは下位のクラス」に分けて整理できることが直感的に理解しやすくなります。

クラス継承の書き方

class構文での基本的な継承の書き方

Pythonで継承を行う基本形はとてもシンプルです。

class 子クラス名(親クラス名):という書き方をするだけです。

シンプルな継承の例

Python
# 親クラス(スーパークラス)
class Animal:
    def speak(self):
        # 共通のメソッド定義
        print("何かが鳴いています")

# 子クラス(サブクラス)
class Dog(Animal):
    pass  # 何も書かなければ、Animalと全く同じ機能を持つ
実行結果
# 利用例(インタラクティブ環境など)
dog = Dog()
dog.speak()  # 親クラスAnimalのspeakメソッドが呼ばれる

このように子クラスが何も定義しなくても、親クラスのメソッドをそのまま使える点が継承の出発点です。

メソッドオーバーライドの書き方と注意点

継承の次のステップはメソッドオーバーライドです。

これは、子クラスで親クラスと同じ名前のメソッドを定義し直し、挙動を変更することを指します。

メソッドオーバーライドの例

Python
class Animal:
    def speak(self):
        print("何かが鳴いています")

class Dog(Animal):
    # メソッドオーバーライド
    def speak(self):
        print("ワン!")

class Cat(Animal):
    # 別のオーバーライド
    def speak(self):
        print("ニャー!")

# 実行例
dog = Dog()
cat = Cat()

dog.speak()  # Dogクラスのspeakが呼ばれる
cat.speak()  # Catクラスのspeakが呼ばれる
実行結果
ワン!
ニャー!

オーバーライド時の主な注意点は次の通りです。

まず、引数のインターフェースをできるだけ変えないことです。

親クラスのメソッドを利用している側のコードが、子クラスに差し替えた途端にエラーになるのを防ぐためです。

また、型アノテーションを使っている場合は、親クラスと整合的になるように意識すると設計の一貫性が保てます。

コンストラクタ(__init__)を継承するときのポイント

コンストラクタ__init__も通常のメソッドと同じように継承されます。

しかし、親クラスの初期化処理をきちんと呼ぶかどうかが非常に重要なポイントになります。

親クラスのコンストラクタを呼ぶ典型例

Python
class Person:
    def __init__(self, name, age):
        # 共通の初期化処理
        self.name = name
        self.age = age

class Employee(Person):
    def __init__(self, name, age, salary):
        # 親クラスの__init__を呼び出して共通部分を初期化
        super().__init__(name, age)
        # 子クラス固有の初期化
        self.salary = salary

# 実行例
employee = Employee("Alice", 30, 500000)
print(employee.name, employee.age, employee.salary)
実行結果
Alice 30 500000

superを使わずに親コンストラクタを呼ばないと、親クラス側で想定している属性が初期化されず、後でエラーになることがあります。

特に継承階層が深くなったり、多重継承をする場合には、superを使った連鎖的な初期化が安全です。

この点については後半で詳しく解説します。

単一継承とクラス階層の図解

Pythonでは、1つの親クラスだけを継承する単一継承がもっともよく使われます。

単一継承のクラス設計は、上から下に向かって徐々に具体的になる階層構造として捉えると理解しやすくなります。

このように階層を図にすると、どのクラスがどの責務を持つべきか、どこまでを共通化するかが視覚的に整理できます。

superの基本と使い方

superとは何か

superは、「継承チェーン上で、次に呼ぶべき親クラスのメソッドを見つけるための仕組み」です。

特に多重継承や深いクラス階層で本領を発揮します。

Pythonのsuper()は単に<parent_class_name.method(self, …)>の糖衣構文ではなく、MRO(Method Resolution Order)と呼ばれるクラス探索順序に基づいて「次のクラス」を決めてくれます。

これにより、複数のクラスが協調して同じメソッドを順番に呼び合う「協調的多重継承」が可能になります。

superで親クラスのメソッドを呼び出す書き方

もっとも基本的な使い方はsuper().メソッド名(引数…)です。

Python 3 ではクラス名やselfを明示する必要はなく、引数なしのsuper()が推奨されます。

親メソッドを拡張するオーバーライドの例

Python
class Animal:
    def speak(self):
        print("何かが鳴いています")

class Dog(Animal):
    def speak(self):
        # 親の処理を先に呼ぶ
        super().speak()
        # 追加の処理を行う
        print("ワン!")

dog = Dog()
dog.speak()
実行結果
何かが鳴いています
ワン!

この例では、親クラスの基本動作を保ちつつ、子クラスで振る舞いを追加しています。

完全に置き換えるのではなく「付け足す」場合には、このパターンがよく使われます。

__init__でsuperを使うときの典型パターン

コンストラクタ__init__では、子クラスで追加した引数を処理しつつ、共通部分は親クラスに任せるのが基本的なパターンです。

典型的な __init__ + super の書き方

Python
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

class AdminUser(User):
    def __init__(self, name, email, permissions):
        # 共通の初期化は親クラスに任せる
        super().__init__(name, email)
        # 子クラス固有の初期化
        self.permissions = permissions

admin = AdminUser("Bob", "bob@example.com", ["read", "write"])
print(admin.name, admin.email, admin.permissions)
実行結果
Bob bob@example.com ['read', 'write']

このように必ずsuper().__init__を呼んで、共通の初期化を親クラスに任せることで、将来的に親クラスの実装が変わっても子クラス側のコード変更を最小限に抑えられます。

superとクラスメソッド・静的メソッドの使い方

superはインスタンスメソッドだけでなく、クラスメソッド静的メソッドとも組み合わせて使えます。

クラスメソッドとsuper

クラスメソッドは@classmethodデコレータを使い、第一引数はclsとなります。

この中から<code>super()</code>を使うと、MROに基づいて次のクラスのクラスメソッドを呼び出せます。

Python
class Base:
    @classmethod
    def describe(cls):
        print(f"Base.describe from {cls.__name__}")

class Child(Base):
    @classmethod
    def describe(cls):
        # Baseのクラスメソッドを呼び出す
        super().describe()
        print(f"Child.describe from {cls.__name__}")

Child.describe()
実行結果
Base.describe from Child
Child.describe from Child

ここで重要なのは、親クラス側でもclsは呼び出し側のクラス(Child)になっている点です。

これにより、クラスメソッドを継承しても柔軟に動作させることができます。

静的メソッドとsuper

静的メソッド@staticmethodはクラスにもインスタンスにも束縛されません。

そのため、実質的には普通の関数と同じですが、それでもsuper経由で呼び出すことは可能です。

Python
class Base:
    @staticmethod
    def util():
        print("Base.util")

class Child(Base):
    @staticmethod
    def util():
        # 親の静的メソッドを呼び出す
        super(Child, Child).util()
        print("Child.util")

Child.util()
実行結果
Base.util
Child.util

インスタンスメソッドやクラスメソッドほど頻度は高くありませんが、静的メソッドをオーバーライドし、その上で親実装も利用したいという場面ではこのような書き方をすることがあります。

superと多重継承(MRO)の関係を図解で理解

superを真に理解するには、MRO(Method Resolution Order)のイメージを持つことが重要です。

MROは「メソッドを探索するときに、クラスをどの順番でたどるか」というルールです。

この図をイメージしておくと、superが「親クラス1つを直接指定する」のではなく、「MRO上の次のクラス」を呼び出す仕組みであることが理解しやすくなります。

これが後述する「協調的多重継承」を成立させる鍵です。

多重継承を図解で理解

多重継承とは何かと使い所

多重継承とは、1つのクラスが複数の親クラスを同時に継承することです。

Pythonではclass 子クラス(親1, 親2, ...):のように、丸括弧の中に複数のクラスをカンマ区切りで並べるだけで多重継承ができます。

多重継承の主な使い所は、「横断的な機能(ミックスイン)を付け足す」場面です。

たとえば「ログ機能」「デバッグ機能」「シリアライズ機能」のように、さまざまなクラスに後付けできる振る舞いを別クラスとして定義し、それを複数のクラスで共有するような使い方です。

一方で、多重継承は設計が複雑になりやすく、安易に使うと保守性が落ちるため、意図を持って慎重に利用することが大切です。

多重継承の書き方と基本例

多重継承の基本コード

Python
class Flyable:
    def fly(self):
        print("空を飛びます")

class Swimmable:
    def swim(self):
        print("水の中を泳ぎます")

# 複数の親クラスを継承
class FlyingFish(Flyable, Swimmable):
    pass

fish = FlyingFish()
fish.fly()
fish.swim()
実行結果
空を飛びます
水の中を泳ぎます

このように、FlyingFishは両方の親クラスのメソッドをそのまま利用できます

ただし、親クラス側で同名のメソッドが存在する場合、MROの順序によってどのクラスのメソッドが呼ばれるかが決まります。

ダイヤモンド継承問題とMRO

多重継承でよく登場する問題がダイヤモンド継承です。

これは、1つのクラスが、共通の祖先クラスを持つ複数のクラスを継承している構造を指します。

ダイヤモンド継承とsuperの例

Python
class Base:
    def process(self):
        print("Base.process")

class Left(Base):
    def process(self):
        print("Left.process start")
        super().process()
        print("Left.process end")

class Right(Base):
    def process(self):
        print("Right.process start")
        super().process()
        print("Right.process end")

class Child(Left, Right):
    def process(self):
        print("Child.process start")
        super().process()
        print("Child.process end")

child = Child()
child.process()
print(Child.mro())  # MROの確認
実行結果
Child.process start
Left.process start
Right.process start
Base.process
Right.process end
Left.process end
Child.process end
[<class '__main__.Child'>, <class '__main__.Left'>, <class '__main__.Right'>, <class '__main__.Base'>, <class 'object'>]

ここで重要なのは、どのクラスでも super().process() を呼んでいるにもかかわらず、Base.process は一度だけ呼ばれている点です。

これはPythonのMROとsuperの連鎖的な動作によるもので、ダイヤモンド継承問題(共通祖先の処理が何度も呼ばれてしまう問題)を防いでくれています。

多重継承とsuperを組み合わせるベストプラクティス

多重継承を使うときのベストプラクティスは、「協調的なsuper呼び出し」を徹底することです。

つまり、関係するすべてのクラスで、同じシグネチャを持つメソッド内でsuper()を呼ぶことで、MROに沿ってメソッドが順番に実行されるようにします。

このパターンを守ると、クラスを追加・差し替えしても全体の動作が壊れにくくなります。

特に「ミックスイン」と呼ばれる横断的な機能を提供するクラス群では、必ずsuperを呼ぶことが非常に重要です。

ミックスイン風の協調的継承例

Python
class LoggerMixin:
    def save(self):
        print("LoggerMixin: 保存前にログを出力します")
        super().save()

class BaseSaver:
    def save(self):
        print("BaseSaver: データを保存しました")

class UserSaver(LoggerMixin, BaseSaver):
    pass

user_saver = UserSaver()
user_saver.save()
print(UserSaver.mro())
実行結果
LoggerMixin: 保存前にログを出力します
BaseSaver: データを保存しました
[<class '__main__.UserSaver'>, <class '__main__.LoggerMixin'>, <class '__main__.BaseSaver'>, <class 'object'>]

LoggerMixinのsaveはsuper().save()を呼ぶことで、後続のBaseSaver.saveへ処理をバトンパスしています。

このように協調的なsuper呼び出しを設計しておくことで、LoggerMixinをほかのクラスと組み合わせても安全に動作させることができます。

クラス継承設計のアンチパターンと回避策

最後に、継承設計で陥りやすいアンチパターンと、その回避策を整理しておきます。

代表的なアンチパターンは次のようなものです。

1つ目は、「なんでもかんでも継承する」ことです。

本来は「別のオブジェクトを持つ(コンポジション/委譲)」べきところまで継承で表現しようとすると、クラス階層が肥大化し、理解と変更が難しくなります。

この場合、HAS-A(〜を持つ)関係はコンポジション、IS-A(〜である)関係だけを継承で表現するのが基本的な指針です。

2つ目は、superを呼ばない多重継承です。

多重継承で同名メソッドを定義しているにも関わらず、どこかのクラスでsuper().method()を呼び忘れると、その先のクラスの処理が完全にスキップされてしまう危険があります。

多重継承を使う場合は、統一されたメソッドシグネチャ + super呼び出しを徹底してください。

3つ目は、親クラスの実装に過度に依存する子クラスです。

子クラスが親クラスの内部実装(特定の属性名や手順)にべったり依存していると、親クラスの変更がすべての子クラスに波及してしまいます。

これを避けるには、親クラスの「公開インターフェース」を明確にし、それ以外にはできるだけ触れないことが大切です。

まとめ

Pythonのクラス継承は、コードの再利用と設計の整理に非常に強力な仕組みです。

基本的な単一継承から始めて、メソッドオーバーライドとsuperによる親メソッドの活用、さらに多重継承とMROに基づく協調的なsuper呼び出しまで理解できれば、現場レベルのクラス設計に十分対応できます。

一方で、継承は乱用すると構造が複雑になりやすいため、IS-A関係だけを継承で表現し、横断的な機能はミックスインやコンポジションで補うというバランス感覚を持つことが重要です。

クラスとオブジェクト指向

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!