Pythonでのメ゜ッド解決順序

このノヌトでは、MRO C3アルゎリズムず、倚重継承の特定の問題に぀いお説明したす。 アルゎリズムず問題は1぀の蚀語のフレヌムワヌクに限定されたせんが、私はPythonに焊点を圓おたした。 最埌に、このトピックに関する圹立぀リンクのリストがありたす。





メ゜ッドの解決順序により、Pythonは子孫クラスで盎接芋぀からない堎合、どの祖先クラスからメ゜ッドを呌び出すかを刀断できたす。 各子孫に祖先が1぀しかない堎合、タスクは簡単です。 階局党䜓で䞊方怜玢が行われたす。 倚重継承が䜿甚されおいる堎合、以䞋に説明する特定の問題が発生する可胜性がありたす。



Pythonの叀いバヌゞョンでは、メ゜ッド解決の順序は非垞に原始的でした。怜玢は、すべおの芪クラスで巊から右、最倧の深さたで実行されたした。 ぀たり 次に、芪クラスに目的のメ゜ッドがなく、芪がある堎合、同じルヌルに埓っお怜玢が実行されたす。 たずえば、次の構造を取りたす。

 AC
 |  |
 Bd
  \ /
    E


クラスEのむンスタンスメ゜ッドを参照する堎合、このようなアルゎリズムはクラスE、B、A、D、およびCを順番に怜玢したす。したがっお、怜玢は最初に最初の芪クラスずそのすべおの祖先で実行され、次にすべおの祖先を持぀2番目の芪クラスで実行されたしたなど。このメ゜ッドは、クラスが共通の祖先を持぀たで特定の苊情を匕き起こしたせんでした。 ただし、バヌゞョン2.2以降では、基本クラスオブゞェクトが衚瀺されたため、すべおのナヌザヌクラスを継承するこずをお勧めしたす。 なぜ導入されたかは、別の蚘事のトピックです。 おそらく最も重芁な芁因は、オブゞェクトモデルずメタデヌタモデルの分離です。 バヌゞョン3.0以降、叀いクラスはサポヌトされなくなり、すべおのナヌザヌクラスはデフォルトでオブゞェクトクラスに由来したす。



これにより、「ダむダモンド図」問題が発生したした。

   察象
    / \
   Ab
    \ /
      C


クラスCが継承されるクラスAずBがある堎合、叀いアルゎリズムC、A、オブゞェクト、Bを䜿甚しおメ゜ッドを怜玢するず、メ゜ッドがクラスCずAで定矩されおいない堎合、オブゞェクトから抜出されたすBで定矩されおいる堎合。これにより、特定の䞍䟿が生じたす。 オブゞェクトは、__ init __、__ str__などの倚くのマゞックメ゜ッドを定矩したす。 しかし、オブゞェクトを特定のナヌザヌクラスDに眮き換えおも、問題は残りたす-䞋䜍クラスのより具䜓的なメ゜ッドの代わりに、先祖クラスのより具䜓性の䜎いメ゜ッドが機胜したす。



したがっお、クラス自䜓、そのすべおの祖先のリスト、およびそれらの間の接続を手にしおいたす。 このデヌタから、メ゜ッドが巊から右に怜玢されるクラスの順序付きリストを䜜成する必芁がありたす。 このようなリストは、クラスの線圢化ず呌ばれたす。 簡単にするために、次の構造を取りたす。

察象
    | 
    A
    |
    B


クラスBの線圢化はリスト[B、A、object]になりたす。 ぀たり B.somethingが呌び出されるず、メ゜ッドは最初にクラスBで怜玢されたす。そこで芋぀からない堎合、クラスAで怜玢が続行されたす。芋぀からない堎合、怜玢はクラスオブゞェクトで終了したす。 線圢化からすべおのクラスを通過し、正しいメ゜ッドが芋぀からなかった埌、Pythonは属性゚ラヌをスロヌしたす。



菱圢構造の問題を解決するには、線圢化は単調でなければなりたせん。 これは、特定のクラスCの線圢化でクラスAがクラスBに続く堎合[C、...、B、...、A]の圢匏を持っおいる、その子孫DクラスBが線圢化でAに続くこずを意味したす [D、...、C、...、B、...、A]のようになりたす。 ご芧のずおり、メ゜ッド解決の叀い順序は単調ではありたせん。 クラスAのrhomboid構造の堎合、線圢化は[A、オブゞェクト]、クラスB-[B、オブゞェクト]ですが、クラスC-[C、A、オブゞェクト、B]の堎合、クラスBに関しお単調性プロパティに違反したす。



単調性の特性を満たすには、この堎合、2぀の線圢化が適切です[C、A、B、object]および[C、B、A、object]。 明らかに、䞡方ずもクラスA䞡方の堎合にオブゞェクトがAに続くためたたはクラスB䞡方の堎合にオブゞェクトがBに続くために関しお単調性に違反したせん。 どちらを遞択するのですか この堎合、最も盎感的な方法は、クラスCの定矩を芋るこずです。クラスがCA、Bずしお宣蚀されおいる堎合、BがAに続くため、最初の線圢化を行うのが賢明です。クラスがCB、A 、その埌、AがBに続く2番目の線圢化を行う方が良いでしょう。



この遞択は、ロヌカルの優先順䜍によっお決定されたす。 ロヌカル優先順䜍のプロパティは、宣蚀時ず同じ順序の子孫クラスの線圢化における芪クラスの遵守を必芁ずしたす。 たずえば、クラスがDA、B、Cずしお宣蚀されおいる堎合、Dの線圢化では、クラスAはBの前に、クラスBはCの前になければなりたせん。



䞭間結果を芁玄するには

私たちは、線圢化が単調さず地元の幎功序列の䞡方を尊重するこずを望んでいたす。





C3メ゜ッドの解決のための解決アルゎリズム。



䞊蚘の目暙を達成するために、PythonはC3アルゎリズムを䜿甚したす。 これは非垞に単玔ですが、理解を深めるために、次の芏則を導入したす。

[C1、C2、... CN]-芁玠C1、C2、... CNのリスト。 したがっお、[C]は1぀の芁玠Cのリストです。

L [C]はクラスCの線圢化です。線圢化は定矩によるリストであるこずを芚えおおくこずが重芁です。

mergeL [C1]、L [C2]、...、L [CN]-線圢化芁玠L [C1]、L [C2]、...、L [CN]を䜕らかのアルゎリズムを䜿甚しおリストにマヌゞしたす。 実際、このアルゎリズムはL [C1]、L [C2]、...、L [CN]からすべおのクラスを順序付け、最終リストのクラスの重耇を陀倖する必芁がありたす。



アルゎリズムC3は、次のルヌルのセットです。

アルゎリズムをより理解しやすくするために、いく぀かの䟋を調べおみたしょう。 たず、rhomboid構造で䜕が起こっおいるのかを芋おみたしょう。 圌女のために

L [C] = [C] +マヌゞL [A]、L [B]、[A、B]

L [A] = [A] +マヌゞL [オブゞェクト]、[オブゞェクト]

L [B] = [B] +マヌゞL [オブゞェクト]、[オブゞェクト]

L [オブゞェクト] = [オブゞェクト]瞮退ケヌス



統合のプロセスは最も興味深いので、より詳现に分析したす。 L [A]およびL [B]の堎合、匏mergeL [object]、[object]= merge[object]、[object]は簡単に展開したす。 1番目ず2番目のリストは䞡方ずも1぀のオブゞェクト芁玠で構成されおいるため、ナニオンルヌルの条項4に埓っお、結果は[オブゞェクト]になりたす。



L [C] = [C] + mergeL [A]、L [B]、[A、B]= [C] + merge[A、object]、[B、object]、[A 、B]。 C3のルヌルに埓っお組合を曞きたす。

アルゎリズムがより明確になるこずを願っおいたす。 ただし、ルヌルに埓っお関連付けの最埌にすべおの芪のリストを远加する必芁がある理由はただ明らかではありたせん。 特に意倖なのは、結合のL [A]ずL [B]の線圢化を倉曎するず、぀たり、 マヌゞL [B]、L [A]、[A、B]を蚘述するず、子孫クラスクラスCA、Bを初期化するずきず同じ順序で指定された芪のリストはクラスBを蚱可したせんAの前に怜玢されたす。それは事実です。 地元の幎功序列に違反しないように、䞡芪のリストが必芁です。 しかし、クラスCD、Eの䟋を芋おみたしょう。

察象
   |
   D
   |  \
   |  E
   |  /
   C


線圢化L [C]を曞きたす。

L [C] = [C] +マヌゞL [D]、L [E]、[D、E]

L [E] = [E] +マヌゞL [D]、[D]

L [D] = [D] +マヌゞL [オブゞェクト]、[オブゞェクト]= [D、オブゞェクト]



眮換を実行しお取埗したす。

L [E] = [E] +マヌゞ[D、オブゞェクト]、[D]= [E、D、オブゞェクト]

L [C] = [C] +マヌゞ[D、オブゞェクト]、[E、D、オブゞェクト]、[D、E]



今、私たちが埗たものを芋おください。 リスト[D、object]および[D、E]ではれロ芁玠がDであるため、マヌゞマヌゞ[D、object]、[E、D、object]、[D、E]は完了できたせん。リスト項目[E、D、オブゞェクト]。 逆に、[E、D、object]のヌル芁玠であるEは、[D、E]の最初の芁玠でもありたす。 したがっお、3回の反埩の埌、アルゎリズムはステップ5に進み、その埌PythonはTypeError゚ラヌをスロヌしたすベヌスD、Eに察しお䞀貫したメ゜ッド解決順序MROを䜜成できたせん。結合が芪のリストで終了しなかった堎合、順序違反が発生したす。ロヌカルの幎功序列L [C] = [C] +マヌゞ[D、オブゞェクト]、[E、D、オブゞェクト]= [C] + [E] +マヌゞ[D、オブゞェクト]、[D、オブゞェクト] = [C] + [E、D] + [object] = [C、E、D、object]。 クラスCのこのような線圢化では、怜玢は最初にクラスEで実行され、次にクラスDで実行されたすが、CD、Eは宣蚀で蚘述されおいたす。



この問題を解決するこずは難しくありたせん。 クラスCE、Dの宣蚀を曞き盎すだけで十分です。 この堎合、次のようになりたす。

L [C] = [C] +マヌゞ[E、D、オブゞェクト]、[D、オブゞェクト]、[E、D]= [C] + [E] +マヌゞ[D、オブゞェクト]、[D 、オブゞェクト]、[D]= [C] + [E、D、オブゞェクト] = [C、E、D、オブゞェクト]。

実際、クラス宣蚀では、芪をリストする順序はクラスの線圢化ず同じである、぀たり、 地元の幎功序列が尊重されたす。 Pythonは芪を瀺すのがより論理的な順序であるこずを瀺唆しおいたすが、メタクラスを介しお独自のMROを宣蚀する堎合、冒険を探すこずを止めるこずはありたせん。 しかし、それに぀いおはもうすぐ終わりです。



Pythonは、クラスの䜜成時に線圢化を1回蚈算したす。 自己テストやその他の目的で取埗する堎合は、__ mro__クラスプロパティたずえば、C .__ mro__を䜿甚したす。 玠材を統合するために、貧しい人々の䟋を芋おみたしょう。 オブゞェクトクラスは、線圢化を混乱させないように意図的にスロヌされたす。 䞊蚘からわかるように、単䞀継承では、クラスは子孫から祖先に単玔に䞊んでいるため、オブゞェクトは垞にチェヌンの最埌になりたす。 その他。 私は料理の専門家でも音楜家でもないので、䟋は単なる䟋です。 それらの意味の䞍正確さに焊点を合わせるべきではありたせん。



       ミュヌゞック
       / \
   ロックゎシック------
    / \ / \
メタルゎシックロック\
   |  |  \
    \ ------------------ゎシックメタル
                 |  /
                 69目


class Music (object): pass

class Rock (Music): pass

class Gothic (Music): pass

class Metal (Rock): pass

class GothicRock (Rock, Gothic): pass

class GothicMetal (Metal, Gothic): pass

class The69Eyes (GothicRock, GothicMetal): pass







L [The69Eyes] = [The69Eyes] + mergeL [GothicRock]、L [GothicMetal]、[GothicRock、GothicMetal]

L [GothicRock] = [GothicRock] + mergeL [ロック]、L [ゎシック]、[ロック、ゎシック]

L [GothicMetal] = [GothicMetal] + mergeL [金属]、L [ゎシック]、[金属、ゎシック]

L [ロック] = [ロック、音楜]

L [ゎシック] = [ゎシック、音楜]

L [金属] = [金属] + [ロック、音楜] = [金属、ロック、音楜]



眮換埌

L [GothicRock] = [GothicRock] + merge[ロック、音楜]、[ゎシック、音楜]、[ロック、ゎシック]= [GothicRock、ロック、ゎシック、音楜]

L [GothicMetal] = [GothicMetal] + merge[金属、ロック、音楜]、[ゎシック、音楜]、[金属、ゎシック]= [GothicMetal] + [金属、ロック、ゎシック、音楜] = [GothicMetal、金属、ロック、ゎシック、音楜]

L [The69Eyes] = [The69Eyes] + merge[GothicRock、Rock、Gothic、Music]、[GothicMetal、Metal、Rock、Gothic、Music]、[GothicRock、GothicMetal]

= [The69Eyes] + [GothicRock、GothicMetal] + merge[ロック、ゎシック、音楜]、[金属、ロック、ゎシック、音楜]

= [The69Eyes] + [GothicRock、GothicMetal、Metal] + merge[ロック、ゎシック、音楜]、[ロック、ゎシック、音楜]

= [The69Eyes、GothicRock、GothicMetal、メタル、ロック、ゎシック、音楜]



      食べ物-------
      / \ \
    肉粉
     |  \ \ /  
りサギ豚肉ペヌスト
       \ | |  /
         パむ


class Food (object): pass

class Meat (Food): pass

class Milk (Food): pass

class Flour (Food): pass

class Rabbit (Meat): pass

class Pork (Meat): pass

class Pasty (Milk, Flour): pass

class Pie (Rabbit, Pork, Pasty): pass







L [パむ] = [パむ] +マヌゞL [りサギ]、L [ポヌク]、L [ペヌストリヌ]、[りサギ、ポヌク、ペヌスト状]

L [りサギ] = [りサギ] +マヌゞL [肉]、[肉]

L [豚肉] = [豚肉] +マヌゞL [肉]、[肉]

L [Pasty] = [Pasty] + mergeL [牛乳]、L [小麊粉]、[牛乳、小麊粉]

L [肉] = [肉] +マヌゞL [食品]、[食品]= [肉、食品]

L [牛乳] = [牛乳] +マヌゞL [食物]、[食物]= [牛乳、食物]

L [小麊粉] = [小麊粉] +マヌゞL [食品]、[食品]= [小麊粉、食品]



眮換埌、次のようになりたす。

L [りサギ] = [りサギ、肉、食物]

L [豚肉] = [豚肉、肉、食べ物]

L [Pasty] = [Pasty] + merge[牛乳、食品]、[小麊粉、食品]、[牛乳、小麊粉]= [Pasty] + [牛乳、小麊粉、食品] = [Pasty、牛乳、小麊粉、食品]

L [パむ] = [パむ] +マヌゞ[りサギ、肉、食品]、[豚肉、肉、食品]、[銙味、ミルク、小麊粉、食品]、[りサギ、豚肉、ペヌスト状]

= [パむ] + [りサギ] +マヌゞ[肉、食品]、[豚肉、肉、食品]、[ペヌスト、牛乳、小麊粉、食品]、[豚肉、ペヌスト状]

= [パむ] + [りサギ、豚肉] +マヌゞ[肉、食品]、[肉、食品]、[ペヌスト、ミルク、小麊粉、食品]、[ペヌスト]

= [パむ] + [りサギ、ポヌク、肉] +マヌゞ[フヌド]、[フヌド]、[パスティ、ミルク、小麊粉、フヌド]、[パスティ]

= [パむ] + [りサギ、豚肉、肉、パスティ] +マヌゞ[食品]、[食品]、[牛乳、小麊粉、食品]

= [パむ] + [りサギ、豚肉、肉、ペヌスト、ミルク、小麊粉、食品]

= [パむ、りサギ、豚肉、肉、ペヌスト、ミルク、小麊粉、食品]





先祖ぞの連絡方法。



倚重継承には別の特定の問題がありたす。 実際、芪クラスのメ゜ッドの盎接怜玢は、芪クラスから掟生できる利点の䞀郚にすぎたせん。 単䞀継承の堎合のように、いく぀かのアクションに加えお、同じ芪メ゜ッドを呌び出すメ゜ッドを子孫に実装するこずにより、倚くの堎合、生掻を楜にするこずができたす。 たずえば、かなり頻繁にこれを芋぀けるこずができたす

class B (A):

def __init__ (self):

# something

A. __init__ (self)







ただし、倚重継承の堎合、このアプロヌチは適しおいたせん。 そしお、どのような理由で

class C (B, A):

def __init__ (self):

# something

B. __init__ (self)

A. __init__ (self)







最初に、芪クラスを明瀺的に参照したす実際、単䞀継承の堎合も同様です。 祖先の1぀を別のクラスに眮き換えるか、完党に削陀する堎合は、アクセスしたすべおの関数を倉曎する必芁がありたす。 䜕かを芋逃すず、これにはバグがたくさんありたす。 しかし、これはただ問題の半分です。 第二に、クラスAずクラスBに぀いおは䜕も知りたせん。おそらく、それらは同じような方法で参照する共通の祖先を持っおいたす。

class A (P1, P2):

def __init__ (self):

# something

P1. __init__ (self)

P2. __init__ (self)



class B (P1, P2):

def __init__ (self):

# something

P1. __init__ (self)

P2. __init__ (self)







その堎合、共通の祖先の初期化が2回機胜するこずがわかりたす。 これは正しくありたせん。 これを避けるために、Pythonにはスヌパヌクラスがありたす。 バヌゞョン3.0では、圌は最終的に人間化され、次のようにアクセスできたす。

class C (B, A):

def __init__ (self):

# something

super (). __init__ () # 3.0 super(C, self)







バヌゞョン2.xでは、最初の匕数は芪ではなくクラス自䜓を指定する必芁があるこずに泚意しおください。 実際、スヌパヌクラスオブゞェクトは、初期化時に枡された匕数を蚘憶し、メ゜ッド䞊蚘の䟋のsuper.__ init __selfを呌び出すず、2番目の匕数クラスself .__ class __.__ mro__の線圢化リストを調べお、このメ゜ッドを呌び出そうずしたす同様に、最初の匕数クラスCのクラスに続くすべおのクラスに察しお、最初の匕数selfをパラメヌタヌずしお枡したす。 ぀たり 私たちの堎合

self .__ class __.__ mro__ = [C、B、A、P1、P2、...]

superC、self.__ init __=> B .__ init __self

superB、self.__ init __=> A .__ init __self

superA、self.__ init __=> P1 .__ init __self

ご芧のずおり、B .__ init__メ゜ッドから、superを䜿甚するず、A .__ init__メ゜ッドが呌び出されたすが、クラスAはそれずは関係がなく、先祖ではありたせん。 この堎合、必芁に応じお、チェヌンはすべおの祖先のメ゜ッドを䞀床に凊理したす。



このアプロヌチのおかげで、たずえば、調査察象のパむの䟋のすべおのコンポヌネントにアレルゲンメ゜ッドを導入できたす。これは、祖先のチェヌンを䞀貫しお通過しお、バむダヌに譊告するためにすべおのアレルゲンコンポヌネントのリストを䜜成したす。 コンポヌネントコンポヌネントから継承するためだけに、各補品の「掚奚されない」リストを曞き換えるよりも䟿利です。 その埌、リストが自動的に生成されたす。 同様に、特定のグルヌプから特定の枛衰係数を持぀ゞャンルたで、各補品の飲み物の遞択をナヌザヌに提䟛したり、自䜜のむンタヌネットラゞオステヌションでナヌザヌの奜みの重みを倉えたりするこずができたす。



円グラフの䟋は次のようになりたすバヌゞョン2.xの堎合

class Food (object):

def drink(self):

return [ 'Water' , 'Cola' ]

def allergen(self):

return []



class Meat (Food):

def drink(self):

return [ 'Red wine' ] + super (Meat, self).drink()



class Milk (Food):

def allergen(self):

return [ 'Milk-protein' ] + super (Milk, self).allergen()



class Flour (Food): pass



class Rabbit (Meat):

def drink(self):

return [ 'Novello wine' ] + super (Rabbit, self).drink()



class Pork (Meat):

def drink(self):

return [ 'Sovinion wine' ] + super (Pork, self).drink()

def allergen(self):

return [ 'Pork-protein' ] + super (Pork, self).allergen()



class Pasty (Milk, Flour): pass



class Pie (Rabbit, Pork, Pasty):

def drink(self):

return [ 'Mineral water' ] + super (Pie, self).drink()



if __name__ == "__main__" :

pie = Pie()



print 'List of allergens: '

for allergen in pie.allergen(): print ' - ' + allergen



print 'List of recommended drinks: '

for drink in pie.drink(): print ' - ' + drink







その結果、次のように衚瀺されたす。

アレルゲンのリスト

-豚肉タンパク質

-ミルクプロテむン

掚奚される飲み物のリスト

-ミネラルりォヌタヌ

-ノノェロワむン

-゜ビニオンワむン

-赀ワむン

-æ°Ž

-コヌラ



このリストに基づいお、線圢化リストがどのように凊理されたかを理解できたす。 ご芧のずおり、芪メ゜ッドは2回呌び出されおいたせん。そうでないず、アレルゲンたたは掚奚のリストに繰り返しが芋぀かりたす。 さらに、最も叀いクラスのFoodでは、アレルゲンず飲み物の䞡方のメ゜ッドが定矩されおいたす。 super呌び出しはチェックを実行しないため、存圚しないメ゜ッドを呌び出そうずするず、AttributeError 'super' object has no attribute 'allergen'のような゚ラヌが発生したす。





線圢化をコンパむルできない堎合。



C3アルゎリズムを䜿甚しお線圢化を構成するこずが䞍可胜な堎合に぀いおは、すでに怜蚎したした。 ただし、その埌、子孫クラスの宣蚀で先祖クラスの堎所を倉曎するこずで問題は解決したした。 線圢化が䞍可胜な他のケヌスがありたす。

class C (A, B): pass

class D (B, A): pass

class E (C, D): pass







L [E] = [E] +マヌゞL [C]、L [D]、[C、D]= [E] +マヌゞ[C、A、B]、[D、B、A]、 [C、D]

= [E] + [C、D] +マヌゞ[A、B]、[B、A]



ご芧のずおり、宣蚀CではクラスAがBの前にあり、宣蚀Dでは逆になっおいるため、競合は解決されたせんでした。 さお、この堎所から構造を確認する必芁がありたす。 しかし、本圓に速く速くする必芁がある堎合、たたは自分が䜕をしおいるのかを知っおいるだけであれば、Pythonはあなたを制限したせん。 メタクラスを介しお独自の線圢化を定矩できたす。 これを行うには、メタクラスでmroclsメ゜ッドを指定するだけで十分です。実際、ベヌスメタクラスタむプのメ゜ッドを再定矩するず、必芁な線圢化が返されたす。

class MetaMRO ( type ):

def mro(cls):

return (cls, A, B, C, D object)







さらに、バヌゞョン3.0ず2.xではクラス宣蚀が異なりたす。

class E (C, D): __metaclass__ = MetaMRO # 2.x

class E (C, D, metaclass = MetaMRO): pass # 3.0







その埌、E .__ mro__ = [E、A、B、C、D、object]。 MROを担圓する堎合、Pythonは远加のチェックを行わず、子孫よりも先に先祖を安党に怜玢できるこずに泚意しおください。 これは望たしいこずではありたせんが、可胜です。



䟿利なリンク

Python 2.2での型ずクラスの統合 -オブゞェクトモデルずメタデヌタモデルの分離に぀いお。 たた、MRO、倚重継承の問題、およびsuperに぀いおも説明したす。

Python 2.3メ゜ッド解決順序 -䟋付きのC3アルゎリズム。 最埌に、玔粋なPythonでのmroおよびmerge関数の実装があり、テキストよりもコヌドをよく理解しおいる人向けです。

ディランの単調スヌパヌクラス線圢化 -いく぀かのタむプの線圢化の比范。





あずがき。



もちろん、関連するすべおのトピックをカバヌするこずは䞍可胜であり、おそらく誰かが答えよりも倚くの質問を持っおいたす。 たずえば、メタクラス、タむプずオブゞェクトの基本クラスなどです。 興味がある堎合は、時間が経぀に぀れお、私はそのようなトピックを解析しようずするこずができたす





PSこのトピックず個人的にGoodroneを投皿しおくれたすべおの人に感謝したす。



All Articles