Python | OOP:super()

Python Python
スポンサーリンク

概要(superは「親の処理を安全に呼び継ぐためのバトン」)

super()は、継承チェーンの中で「次に呼ぶべきクラス」のメソッドへバトンを渡す仕組みです。単なる“親クラス呼び出し”ではなく、PythonのMRO(メソッド解決順序)に従って適切な相手に渡します。重要なのは、初期化でのsuper().init、多重継承での“協調的super”、そして「親の契約を守りながら差分だけ足す」使い方です。


基本の使い方(親の初期化とメソッド拡張)

親の初期化を呼んでから子の拡張を足す

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

class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name)     # 親の初期化(必須属性の準備)
        self.breed = breed         # 子の追加

    def speak(self) -> str:
        base = "..."               # 親を拡張するなら親の実装を呼ぶこともある
        return "ワン!"
Python

親が定める前提(必須属性や検証)を保つため、子でもsuper().initを確実に呼びます。メソッドでも同様に、親の処理を呼んだ上で差分を足すと安全です。

親のメソッドを呼び継ぎ、振る舞いを拡張する

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

class Dog(Animal):
    def speak(self) -> str:
        base = super().speak()   # 親の結果を生かす
        return base + " ワン!"
Python

「既存の仕様を保ちながら強化する」場面で、super().method()が最もシンプルに効きます。


superの本質(MROと“次を呼ぶ”という設計)

MRO(メソッド解決順序)に沿った“次のクラス”へのバトン

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

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

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

class D(B, C):
    def setup(self):
        print("D")
        super().setup()

D().setup()
# 出力: D → B → C → A
# D.mro() == [D, B, C, A, object]
Python

super()は「親」固定ではなく、MRO上の“次のクラス”を呼びます。これにより多重継承でも重複なく一巡できます。

協調的super(各クラスがsuperを呼ぶ前提で設計)

多重継承では、関係するすべてのクラスが「自分の処理後に必ずsuperを呼ぶ」約束で設計すると、順序通りに全メソッドが呼ばれます。1つでもsuperを呼ばないクラスがあると、そこで鎖が途切れ、他の処理が実行されません。


実務での型(初期化・ミックスイン・サブクラス差分)

初期化は軽く、親の準備→子の追加の順番で

class BaseClient:
    def __init__(self, api_key: str, timeout: float = 5.0):
        import requests
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {api_key}"})

class EUClient(BaseClient):
    def __init__(self, api_key: str, timeout: float = 7.0):
        super().__init__(api_key, timeout)  # 親のセッション準備を使う
        self.region = "EU"
Python

親が提供する共通準備(セッションやヘッダー)を受け継ぎ、子では差分だけ持ちます。

ミックスインで“小さな能力”を重ねる

import time, requests

class RetryMixin:
    def request_with_retry(self, call, tries=3, base=0.5):
        for i in range(tries):
            try:
                return call()
            except requests.RequestException:
                time.sleep(base * (2 ** i))
        raise

class Client(BaseClient, RetryMixin):
    def get_json(self, url: str) -> dict:
        def call():
            r = self.session.get(url, timeout=self.timeout)
            r.raise_for_status()
            return r.json()
        return self.request_with_retry(call)
Python

ミックスインは状態を持ち過ぎず、superで連携できる薄い機能として設計すると安全です。


重要ポイントの深掘り(正しい呼び方・引数・Pythonの仕様差)

引数なしsuper()を使う(Python 3の標準)

Python 3では、メソッド内で引数なしのsuper()が推奨です。selfやクラス名を渡す必要はありません。可読性と安全性の面でも、現代的な書き方に寄せましょう。

子のシグネチャは親の契約を守る

親が受け取る引数・返す型の契約を子でも維持します。勝手に意味を変えると置き換え可能性が壊れ、下流でバグが増えます。追加が必要ならデフォルト引数や辞書へのキー追加など、互換を保つ形に。

superを呼ぶ順序と場所

初期化では「super().init → 子の属性設定」の順が基本です。メソッドでは「前処理→super呼び出し→後処理」のどれが適切かを機能の意味で決めます(ログを先に、整形を後に、など)。


よくある落とし穴と対策(呼び忘れ・直接親呼び・多重継承の断線)

super().initの呼び忘れ

親が準備する必須属性が欠落し、実行時にAttributeErrorを招きます。テンプレート化し、コードレビューで“必ず呼ぶ”ことを確認します。

直接親を呼ぶ(Parent.method(self))は避ける

MROを無視して特定の親だけを呼ぶと、他の親に処理が回りません。多重継承で簡単に破綻します。常にsuper()を使い、チェーンを途切れさせない設計にします。

superの未協調(どこかが呼ばない)

多重継承のどこかでsuperを呼ばないと、そこから先が実行されません。ミックスイン含め「全クラスがsuperを呼ぶ」前提で作るのが鉄則です。


例題(ログ→検証→実行の順でsuperを活かすテンプレート)

前処理・親の処理・後処理を安全に鎖でつなぐ

class BaseHandler:
    def handle(self, payload: dict) -> dict:
        # 親の基本処理(検証や共通整形)
        if "id" not in payload:
            raise ValueError("id必須")
        return {"id": payload["id"], "ok": True}

class LoggingHandler(BaseHandler):
    def handle(self, payload: dict) -> dict:
        print("[start]", payload)           # 前処理(ログ)
        base = super().handle(payload)      # 親(検証・整形)
        base["logged"] = True               # 後処理(付加情報)
        print("[done]", base)
        return base
Python

この型だと、前後の付加処理を足しつつ、親の契約(検証)を確実に通過できます。さらにミックスインでリトライやキャッシュも重ねられます。


まとめ(superは「親を固定で呼ぶ」のではなく、MROで“次へ渡す”)

super()の本質は、MROに沿って“次のクラス”へ処理を渡す安全な仕組みです。初期化ではsuper().initで親の準備を確実に呼び、メソッドでは親の処理を尊重しつつ差分を足す。多重継承では協調的superで鎖を途切れさせない。直接親呼びは避け、契約(引数・戻り値)を守る。この型を体に入れれば、初心者でも継承を「壊れず、拡張しやすい」形で設計できるようになります。

タイトルとURLをコピーしました