タイトル: 継承の構文
SEOタイトル: Python 継承の構文完全ガイド(super / MRO / 多重継承 / Mixin / Protocol)
| この記事の要点 |
|
基本: 単一継承
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を推奨。