Pythonにおける継承と動的メソッド解決の仕組み

クラスの継承とメソッド探索の基本

Pythonにおける継承は、既存のクラス(親クラス、基底クラス)の性質を受け継ぎ、新たなクラス(子クラス、派生クラス)を定義する仕組みです。メソッドを呼び出す際、インタプリタはまずインスタンス自身のクラス内を検索し、該当するメソッドが見つからない場合に限り親クラスへと探索を移します。

この挙動において最も重要な原則は、selfが指す対象です。selfはメソッドが定義されているクラスそのものではなく、メソッド呼び出しを行ったインスタンスのクラスを指します。したがって、メソッドの探索は常にインスタンスの型を起点に行われます。

基本的な継承とアクセス範囲

以下の例では、CoreSystemを親クラスとし、ExtendedSystemを子クラスとして定義しています。

class CoreSystem:
    def initialize(self):
        print('System Initialized')

class ExtendedSystem(CoreSystem):
    def execute(self):
        print('Executing Task')

# 親クラスのインスタンスを生成
base_instance = CoreSystem()
base_instance.initialize()  # CoreSystem.initialize が実行される
# base_instance.execute()    # エラー: CoreSystemは子クラスのメソッドを認識しない

# 子クラスのインスタンスを生成
extended_instance = ExtendedSystem()
extended_instance.initialize()  # 親クラスのメソッドを継承して実行
extended_instance.execute()      # 子クラス固有のメソッドを実行

子クラスは親クラスの機能にアクセスできますが、親クラスは子クラスで定義された機能を知ることはできません。

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

子クラスが親クラスと同名のメソッドを定義した場合、子クラスの定義が優先されます。これをオーバーライドと呼びます。

class DataSource:
    def fetch(self):
        print('Fetching from generic source')

class Database(DataSource):
    def fetch(self):
        print('Fetching from database')

    def connect(self):
        print('Connecting to DB')

instance = Database()
instance.fetch()   # Database.fetch が呼び出される
instance.connect() # Database.connect が呼び出される

selfを用いた動的なメソッド解決

継承において理解しておくべき重要な挙動は、親クラスのメソッド内でselfを介して別のメソッドを呼び出した場合の動きです。selfはインスタンスに紐付いているため、呼び出し元のコードが親クラスに記述されていても、実際に実行されるのはインスタンスのクラス(またはその継承チェイン)で定義されたメソッドです。

class Controller:
    def action(self):
        print('Controller Action')
        self.log()  # self.log()の行き先はインスタンスの型によって変わる

    def log(self):
        print('Standard Log')

class CustomController(Controller):
    def action(self):
        print('Custom Action')
        self.log()

    def log(self):
        print('Custom Log')

# 検証1: 親クラスのインスタンス
obj1 = Controller()
obj1.action()
# 出力:
# Controller Action
# Standard Log
# 説明: obj1はControllerのインスタンスなので、self.log()はController.log()を呼び出す

# 検証2: 子クラスのインスタンス
obj2 = CustomController()
obj2.action()
# 出力:
# Custom Action
# Custom Log
# 説明: obj2はCustomControllerのインスタンス。
# self.action()の中でself.log()が呼ばれる際、
# self(obj2)のクラスであるCustomControllerを起点にlog()が探索される。
# そのため、親クラスController.log()ではなく、CustomController.log()が実行される。

多重継承とメソッド解決順序(MRO)

Pythonは多重継承をサポートしています。この際、メソッドはクラス定義の順序(左から右)に従って探索されます。これを利用したMixinパターンでは、機能を動的に差し替えることが可能です。

class BaseWorker:
    def run_task(self):
        self.process()

    def process(self):
        print('Processing in BaseWorker')

class AsyncMixin:
    def process(self):
        print('Processing in AsyncMixin')

class HybridWorker(AsyncMixin, BaseWorker):
    pass

worker = HybridWorker()
worker.run_task()
# 出力: Processing in AsyncMixin
#
# 解説:
# 1. worker.run_task() が呼ばれる
# 2. HybridWorkerにはrun_taskがないため、BaseWorker.run_task()が見つかる
# 3. BaseWorker.run_task() 内部で self.process() が呼ばれる
# 4. この時点でのselfはHybridWorkerのインスタンス
# 5. process()の探索がHybridWorkerから始まる
# 6. HybridWorkerの継承リストは(AsyncMixin, BaseWorker)
# 7. まずAsyncMixinを探索し、process()が見つかるため、それが実行される

このように、selfを通じたメソッド呼び出しの行き先は、記述された場所ではなく、インスタンスのクラス構造によって決定されます。

7月22日 00:20 投稿