14.

Python 継承の構文完全ガイド(super / MRO / 多重継承 / Mixin / Protocol)

編集
この記事の要点
  • Python の継承は class Child(Parent):。複数親も class C(A, B): で可能(多重継承)
  • 親メソッド呼び出しは super().method()。Python 3 では引数不要
  • 多重継承の解決順MRO (Method Resolution Order)Cls.__mro__ で確認、アルゴリズムは C3 線形化
  • 抽象クラス: abc.ABC を継承し @abstractmethod。Mixin は機能切り出し用
  • Protocol (PEP 544, Python 3.8+) で構造的サブタイピング(duck typing を型として表現)

基本: 単一継承

class Animal:
    def __init__(self, name: str):
        self.name = name

    def speak(self) -> str:
        return "..."

class Dog(Animal):              # ★ 継承
    def speak(self) -> str:     # オーバーライド
        return "Woof!"

d = Dog("Pochi")
print(d.name)        # Pochi  (親の __init__)
print(d.speak())     # Woof!  (子のメソッド)

# isinstance / issubclass
print(isinstance(d, Dog))     # True
print(isinstance(d, Animal))  # True
print(issubclass(Dog, Animal)) # True

super() で親メソッドを呼ぶ

class Animal:
    def __init__(self, name: str):
        self.name = name
        self.alive = True

    def describe(self) -> str:
        return f"Animal({self.name})"

class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name)       # ★ 親の __init__ を呼ぶ
        self.breed = breed

    def describe(self) -> str:
        # 親の describe を拡張
        return super().describe() + f", breed={self.breed}"

d = Dog("Pochi", "Shiba")
print(d.describe())
# Animal(Pochi), breed=Shiba

多重継承と Diamond Problem

class A:
    def greet(self): print("A")

class B(A):
    def greet(self):
        print("B")
        super().greet()

class C(A):
    def greet(self):
        print("C")
        super().greet()

class D(B, C):         # ★ 多重継承 (Diamond)
    def greet(self):
        print("D")
        super().greet()

D().greet()
# D
# B
# C
# A

「D → B → C → A」の順で呼ばれます。これは C3 線形化アルゴリズムで決定される MRO の結果です。

MRO (Method Resolution Order)

# クラスの MRO を確認
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

print(D.mro())
# 同じ結果

# C3 線形化の規則:
# 1. 子クラスは親クラスより前に来る
# 2. 多重継承の左から右の順序が保たれる
# 3. 単調性(複数の MRO で順序矛盾があると TypeError)

# ❌ MRO 不能な継承(順序矛盾)
class X(A, B): pass
class Y(B, A): pass
# class Z(X, Y): pass  # TypeError: Cannot create a consistent MRO

抽象クラス (abc.ABC)

from abc import ABC, abstractmethod

class Animal(ABC):              # ★ 抽象基底クラス
    @abstractmethod
    def speak(self) -> str:
        ...                     # 実装を要求

    def describe(self) -> str:
        return f"I say: {self.speak()}"

# Animal は直接インスタンス化できない
# a = Animal()  # TypeError: Can't instantiate abstract class

class Dog(Animal):
    def speak(self) -> str:
        return "Woof!"

d = Dog()
print(d.describe())  # I say: Woof!

# 未実装のままだとインスタンス化エラー
class Cat(Animal):
    pass
# c = Cat()  # TypeError: Can't instantiate abstract class Cat

Mixin パターン

機能の再利用を目的とした「Mix-in 用クラス」を多重継承で組み合わせる Python の定番パターン。

class JsonMixin:
    """to_json() を提供する Mixin"""
    def to_json(self) -> str:
        import json
        return json.dumps(self.__dict__)

class TimestampMixin:
    """created_at / updated_at を持つ Mixin"""
    def __init__(self):
        from datetime import datetime
        now = datetime.now()
        self.created_at = now
        self.updated_at = now

class User(JsonMixin, TimestampMixin):
    def __init__(self, name: str):
        super().__init__()          # TimestampMixin.__init__
        self.name = name

u = User("taro")
print(u.to_json())
# {"created_at": "...", "updated_at": "...", "name": "taro"}

@dataclass(frozen=True) と継承

from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int

@dataclass(frozen=True)
class ColoredPoint(Point):
    color: str = "black"

p = ColoredPoint(1, 2, "red")
print(p)        # ColoredPoint(x=1, y=2, color='red')
# p.x = 100     # FrozenInstanceError(イミュータブル)

Protocol (構造的サブタイピング, PEP 544)

Java の interface 相当ですが、明示的な継承宣言が不要。同名同シグネチャのメソッドを持てば自動的にサブタイプ扱いになります(duck typing を型として表現)。

from typing import Protocol

class Speaker(Protocol):
    def speak(self) -> str: ...

class Dog:                          # Speaker を継承していない!
    def speak(self) -> str:
        return "Woof!"

class Cat:
    def speak(self) -> str:
        return "Meow!"

def announce(s: Speaker) -> None:   # 型ヒントで Protocol を要求
    print(s.speak())

announce(Dog())   # Woof!   ← 継承不要で型チェック通過
announce(Cat())   # Meow!

# Protocol を継承して @runtime_checkable を付けると isinstance も可能
from typing import runtime_checkable
@runtime_checkable
class Speaker2(Protocol):
    def speak(self) -> str: ...

print(isinstance(Dog(), Speaker2))  # True

PEP 526 Variable Annotations と継承

class Base:
    name: str                       # クラス変数アノテーション
    count: int = 0                  # デフォルト値あり

class Sub(Base):
    name: str = "default"           # オーバーライド可
    extra: list[str] = []

# 型は実行時に強制されないが、mypy / pyright で静的検査される

private / protected の表現

class User:
    def __init__(self):
        self.name = "public"        # public
        self._email = "protected"   # _ 始まり: 慣習的 protected(強制力なし)
        self.__password = "private" # __ 始まり: name mangling で実質 private

u = User()
print(u.name)        # OK
print(u._email)      # OK だが「触らない約束」
# print(u.__password)  # ❌ AttributeError

# __ 変数は _ClassName__var にリネームされる
print(u._User__password)  # private(外部からアクセス可能、しかし規約違反)

FAQ

Q: Python 2 と Python 3 での super() の違い
A: Python 2 は super(Child, self).method() と明示的だが、Python 3 は super().method() でクラスと self を省略可。

Q: 親クラスの全メソッドを継承しつつ 1 つだけ無効化したい
A: そのメソッドだけオーバーライドして raise NotImplementedError() を投げます。設計としては継承よりコンポジションを検討すべきサイン。

Q: 多重継承を使うべきか?
A: Mixin パターン以外は避けるのが無難。複雑な MRO は将来のメンテで地獄を見ます。代わりにコンポジション (has-a) + Protocolを推奨。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本的なルール
  2. 変数
  3. 演算子
  4. 標準ライブラリ
  5. 外部ライブラリ
  6. 制御構文
  7. リスト(配列)
  8. タプル
  9. セット
  10. 辞書(dict)
  11. クラスとメソッド
  12. 継承の概念と必要性
  13. 継承の構文
  14. コンストラクタ
  15. cookieの値の設定と取得
  16. 例外処理
  17. 例外を文字列で出力する方法
  18. httpリクエスト(curl)をする方法
  19. Responseオブジェクトの中身の確認
  20. 変数が空かどうか判定する方法
  21. タイムゾーンの設定と現在日時の取得と文字列化
  22. シングルクォーテーションとダブルクォーテーションの違い

最近更新/作成されたページ