メソッドの検索順序

Python2.3以降の新しいクラスは、メソッドの検索順序(MRO, Method Resolution Order)が「C3線形化」というアルゴリズムに変更されている。このアルゴリズムについて解説する。

まずは実例。

>>> class A(object): x = "a"
>>> class B(A): pass
>>> class C(A): x = "c"
>>> class D(object): x = "d"
>>> class E(B, C, D): pass
>>> e = E()
>>> e.x
'c'

クラスの検索順序が深さ優先探索ではないことがわかると思う。

C3線形化の特徴はメソッドを探す順番が

  • クラスXがYを継承しているなら必ずXがYより先にくる
  • クラスXがY, Zという順で継承しているならYは必ずZより先に来る
  • 他のクラスのメソッド探索順でXがYより先に来ているなら、他のクラスの探索順でも必ずXはYの前にある

という3つの条件を満たすことである。

かりにメソッド探索の順番が深さ優先探索だと、上のE.xはまずEの中で探して、次にBの中を探し、次にBの親クラスであるAの中を探す。そうすると実際にはCがAを継承しているにもかかわらずAが先に検索されてしまうためC.xでオーバーライドされているのが反映されない。これがPython2.2以前の挙動。

幅優先でない証拠は下記

>>> class A(object): x = "a"
>>> class B(A): pass
>>> class C(object): x = "c"
>>> class D(B, C): pass
>>> d = D()
>>> d.x
'a'

幅優先探索ならD→B→C→Aの順に検索されることになるが、それではBはAからxを継承しているはずなのにC.xの値が返ってしまう。Pythonは実際にはD→B→A→Cの順に探索している。

まずクラスがどういう順になっているかというリスト(CPL)を作るアルゴリズム

  • [B, A, object]
  • [C, object]
  • [D, B, C]

これを並べるときに、まずobjectの部分がマージされて

  • [((B, A) | C), object]

こんな感じになる。ここで(B, A)とCのどっちを前にするか考えるわけだけど、[D, B, C]の中でBが先になっているから

  • [B, A, C, object]

こうなる。最後にこれと無矛盾なD, B, Cがマージされて

  • [D, B, A, C, object]

こうなる。

最初の例は

  • [B, A, object]
  • [C, A, object]
  • [D, object]
  • [E, B, C, D]

だった。これも

  • [((B, A) | (C, A) | D), object]

こうなって、BやCよりDがあとなので

  • [(B | C), A, D, object]

こうなって、BがCより前なので

  • [B, C, A, D, object]

こうなって最後に

  • [E, B, C, A, D, object]

こうなる。

で、いいのかな?ちがうかな。

クラスが定義されたときに順番が決まるべきか。
つまり、

  • [A, object]

がまず決まって、次にB(A)で

  • [B, A, object]

C(A)で

  • [C, A, object]

となって、最後にD(B, C)で

  • [D,

まで来たときに、どれを選ぶか、と言う話でD(B,C)だからBが選ばれ

  • [D, B

となり、次はAとCのどちらを選ぶかという話になって、CのCPLにAが含まれているからCが先に来て

  • [D, B, C, A, object]

ってなるんかな。

2番目の例だと

  • [A, object]
  • [B, A, object]
  • [C, A, object]
  • [D, object]
  • [E,

でE(B, C, D)だからBが選ばれ

  • [E, B,

Bの次のAとCとDだと、CのCPLがAを含んでいるので先に来て

  • [E, B, C,

Bの次のAとDだと、DのCPLはAを含んでいなくてBが含んでいるのでE(B, C, D)の順に従ってAが選ばれ

  • [E, B, C, A,

Aの次のobjectとDだとDのCPLがobjectを含んでいるのでDが先に来て

  • [E, B, C, A, D, object]

となるのか。

うん、こっちの方が実装として普通そうだ。

        • -

The Python 2.3 Method Resolution Order
http://www.python.org/download/releases/2.3/mro/index.html#the-c3-method-resolution-order

ここの説明がとてもわかりやすかった。