mro.md
1> **Источник:** https://python-all.ru/3.14/howto/mro.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Порядок разрешения методов Python 2.389> **Примечание**10>11> Это исторический документ, предоставленный в качестве приложения к официальной документации. Порядок разрешения методов, описанный здесь, был *введён* в Python 2.3, но всё ещё используется в более поздних версиях, включая Python 3.1213Автор: [Микеле Симонато](https://python-all.ru/3.14/howto/mro.html).1415**Аннотация:**1617*Этот документ предназначен для программистов Python, которые хотят понять порядок разрешения методов C3, используемый в Python 2.3. Хотя он не рассчитан на новичков, он достаточно педагогичен и содержит много проработанных примеров. Мне не известно о других общедоступных документах такого же охвата, поэтому он должен быть полезен.*1819Отказ от ответственности:2021> *Я передаю этот документ Python Software Foundation по лицензии Python 2.3. Как обычно в таких случаях, я предупреждаю читателя, что последующее* должно *быть корректным, но я не даю никаких гарантий. Используйте на свой страх и риск!*2223Благодарности:2425> *Всем участникам списка рассылки Python, которые меня поддерживали. Полу Фоли, указавшему на различные неточности и побудившему меня добавить раздел о локальном порядке предшествования. Дэвиду Гуджеру за помощь с форматированием в reStructuredText. Дэвиду Мерцу за помощь с редактированием. И, наконец, Гвидо ван Россуму, который с энтузиазмом добавил этот документ на официальную страницу Python 2.3.*2627## Начало2829> *Felix qui potuit rerum cognoscere causas* – Вергилий3031Всё началось с сообщения Самуэле Педрони в список рассылки разработчиков Python [\[1\]](https://python-all.ru/3.14/howto/mro.html#id4). В своём сообщении Самуэле показал, что порядок разрешения методов Python 2.2 не является монотонным, и предложил заменить его на порядок разрешения методов C3. Гвидо согласился с его аргументами, и теперь Python 2.3 использует C3. Сам метод C3 не имеет никакого отношения к Python, поскольку он был изобретён людьми, работавшими над Dylan, и описан в статье, предназначенной для лисперов [\[2\]](https://python-all.ru/3.14/howto/mro.html#id5). Настоящая статья даёт (надеюсь) читаемое обсуждение алгоритма C3 для питонистов, желающих понять причины изменений.3233Прежде всего, позвольте отметить, что сказанное относится только к *классам нового стиля*, введённым в Python 2.2: *классические классы* сохраняют свой старый порядок разрешения методов – в глубину, затем слева направо. Поэтому для классических классов не происходит нарушения работы старого кода; и даже если в принципе могло бы быть нарушение для классов нового стиля Python 2.2, на практике случаи, в которых порядок разрешения C3 отличается от порядка разрешения методов Python 2.2, настолько редки, что не ожидается никакого реального нарушения. Поэтому:3435> *Не бойтесь!*3637Более того, если вы не используете активно множественное наследование и у вас нет нетривиальных иерархий, вам не нужно понимать алгоритм C3, и вы можете смело пропустить эту статью. С другой стороны, если вы действительно хотите узнать, как работает множественное наследование, эта статья для вас. Хорошая новость в том, что всё не так сложно, как может показаться.3839Начнём с некоторых базовых определений.40411. Для класса C в сложной иерархии множественного наследования указать порядок, в котором методы переопределяются, т.е. указать порядок предков C, является нетривиальной задачей.422. Список предков класса C, включая сам класс, упорядоченный от ближайшего предка к самому дальнему, называется списком предшествования класса или *линеаризацией* C.433. *Порядок разрешения методов* (MRO) – это набор правил, которые строят линеаризацию. В литературе по Python выражение «MRO C» также используется как синоним линеаризации класса C.444. Например, в случае иерархии одиночного наследования, если C является подклассом C1, а C1 – подклассом C2, то линеаризация C – это просто список \[C, C1, C2\]. Однако в иерархиях множественного наследования построение линеаризации более громоздко, поскольку сложнее построить линеаризацию, которая соблюдает *локальный порядок предшествования* и *монотонность*.455. Локальный порядок предшествования я обсужу позже, но здесь я могу дать определение монотонности. MRO является монотонным, когда выполняется следующее: *если C1 предшествует C2 в линеаризации C, то C1 предшествует C2 в линеаризации любого подкласса C*. В противном случае безобидная операция создания нового класса может изменить порядок разрешения методов, потенциально внося очень тонкие ошибки. Примеры, когда это происходит, будут показаны позже.466. Не все классы допускают линеаризацию. В сложных иерархиях встречаются случаи, когда невозможно создать класс, линеаризация которого удовлетворяет всем желаемым свойствам.4748Вот пример такой ситуации. Рассмотрим иерархию4950```python51>>> O = object52>>> class X(O): pass53>>> class Y(O): pass54>>> class A(X,Y): pass55>>> class B(Y,X): pass56```5758что можно представить следующим графом наследования, где я обозначил через O класс `object`, который является началом любой иерархии для классов нового стиля:5960> ```text61> -----------62> | |63> | O |64> | / \ |65> - X Y /66> | / | /67> | / |/68> A B69> \ /70> ?71> ```7273В этом случае невозможно вывести новый класс C из A и B, поскольку X предшествует Y в A, но Y предшествует X в B, поэтому порядок разрешения методов был бы неоднозначным в C.7475Python 2.3 в такой ситуации вызывает исключение (TypeError: конфликт MRO среди базовых классов Y, X), запрещая неопытному программисту создавать неоднозначные иерархии. Python 2.2 вместо этого не вызывает исключение, а выбирает *специфический* порядок (в данном случае CABXYO).7677## Порядок разрешения методов C37879Введём несколько простых обозначений, которые будут полезны для дальнейшего обсуждения. Я буду использовать сокращённую запись:8081```python82C1 C2 ... CN83```8485для обозначения списка классов \[C1, C2, … , CN\].8687*Голова* списка – это первый элемент:8889```python90head = C191```9293а *хвост* – это остаток списка:9495```python96tail = C2 ... CN.97```9899Также я буду использовать обозначение:100101```python102C + (C1 C2 ... CN) = C C1 C2 ... CN103```104105для обозначения суммы списков \[C\] + \[C1, C2, … ,CN\].106107Теперь я могу объяснить, как работает MRO в Python 2.3.108109Рассмотрим класс C в иерархии множественного наследования, где C наследует от базовых классов B1, B2, … , BN. Нам нужно вычислить линеаризацию L\[C\] класса C. Правило таково:110111> *линеаризация C – это сумма C и слияния линеаризаций родителей и списка родителей.*112113В символьной записи:114115```python116L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)117```118119В частности, если C является классом `object`, не имеющим родителей, линеаризация тривиальна:120121```python122L[object] = object.123```124125Однако в общем случае необходимо вычислить слияние в соответствии со следующим предписанием:126127> *взять голову первого списка, то есть L\[B1\]\[0\]; если эта голова не находится в хвосте ни одного из других списков, то добавить её к линеаризации C и удалить её из списков в слиянии, в противном случае посмотреть на голову следующего списка и взять её, если она является подходящей головой. Затем повторять операцию, пока все классы не будут удалены или не станет невозможно найти подходящие головы. В этом случае невозможно построить слияние, Python 2.3 откажется создавать класс C и вызовет исключение.*128129Это предписание гарантирует, что операция слияния *сохраняет* порядок, если порядок может быть сохранён. С другой стороны, если порядок не может быть сохранён (как в примере серьёзного рассогласования порядка, рассмотренного выше), то слияние не может быть вычислено.130131Вычисление слияния тривиально, если у C только один родитель (одиночное наследование); в этом случае:132133```python134L[C(B)] = C + merge(L[B],B) = C + L[B]135```136137Однако в случае множественного наследования всё сложнее, и я не ожидаю, что правило можно понять без пары примеров ;-)138139## Примеры140141Первый пример. Рассмотрим следующую иерархию:142143```python144>>> O = object145>>> class F(O): pass146>>> class E(O): pass147>>> class D(O): pass148>>> class C(D,F): pass149>>> class B(D,E): pass150>>> class A(B,C): pass151```152153В этом случае граф наследования можно изобразить так:154155> ```text156> 6157> ---158> Level 3 | O | (more general)159> / --- \160> / | \ |161> / | \ |162> / | \ |163> --- --- --- |164> Level 2 3 | D | 4| E | | F | 5 |165> --- --- --- |166> \ \ _ / | |167> \ / \ _ | |168> \ / \ | |169> --- --- |170> Level 1 1 | B | | C | 2 |171> --- --- |172> \ / |173> \ / \ /174> ---175> Level 0 0 | A | (more specialized)176> ---177> ```178179Линеаризации O, D, E и F тривиальны:180181```python182L[O] = O183L[D] = D O184L[E] = E O185L[F] = F O186```187188Линеаризация B может быть вычислена так:189190```python191L[B] = B + merge(DO, EO, DE)192```193194Мы видим, что D – подходящая голова, поэтому мы берём её и сводим задачу к вычислению `merge(O,EO,E)`. Теперь O не является подходящей головой, так как он находится в хвосте последовательности EO. В этом случае правило говорит, что нужно перейти к следующей последовательности. Затем мы видим, что E – подходящая голова; мы берём её и сводим задачу к вычислению `merge(O,O)`, что даёт O. Следовательно:195196```python197L[B] = B D E O198```199200Используя ту же процедуру, находим:201202```python203L[C] = C + merge(DO,FO,DF)204 = C + D + merge(O,FO,F)205 = C + D + F + merge(O,O)206 = C D F O207```208209Теперь мы можем вычислить:210211```python212L[A] = A + merge(BDEO,CDFO,BC)213 = A + B + merge(DEO,CDFO,C)214 = A + B + C + merge(DEO,DFO)215 = A + B + C + D + merge(EO,FO)216 = A + B + C + D + E + merge(O,FO)217 = A + B + C + D + E + F + merge(O,O)218 = A B C D E F O219```220221В этом примере линеаризация упорядочена довольно хорошо в соответствии с уровнем наследования, в том смысле, что более низкие уровни (т.е. более специализированные классы) имеют более высокий приоритет (см. граф наследования). Однако это не общий случай.222223Я оставляю читателю в качестве упражнения вычислить линеаризацию для моего второго примера:224225```python226>>> O = object227>>> class F(O): pass228>>> class E(O): pass229>>> class D(O): pass230>>> class C(D,F): pass231>>> class B(E,D): pass232>>> class A(B,C): pass233```234235Единственное отличие от предыдущего примера – изменение B(D,E) на B(E,D); однако даже такая небольшая модификация полностью меняет порядок иерархии:236237> ```text238> 6239> ---240> Level 3 | O |241> / --- \242> / | \243> / | \244> / | \245> --- --- ---246> Level 2 2 | E | 4 | D | | F | 5247> --- --- ---248> \ / \ /249> \ / \ /250> \ / \ /251> --- ---252> Level 1 1 | B | | C | 3253> --- ---254> \ /255> \ /256> ---257> Level 0 0 | A |258> ---259> ```260261Обратите внимание, что класс E, находящийся на втором уровне иерархии, предшествует классу C, находящемуся на первом уровне, то есть E более специализирован, чем C, даже несмотря на то, что он находится на более высоком уровне.262263Ленивый программист может получить MRO непосредственно из Python 2.2, так как в этом случае он совпадает с линеаризацией Python 2.3. Достаточно вызвать метод [`mro()`](https://python-all.ru/3.14/reference/datamodel.html#type.mro) класса A:264265```python266>>> A.mro()267[<class 'A'>, <class 'B'>, <class 'E'>,268<class 'C'>, <class 'D'>, <class 'F'>,269<class 'object'>]270```271272Наконец, рассмотрим пример из первого раздела, где был серьёзный конфликт порядка. В этом случае линеаризации O, X, Y, A и B вычисляются без труда:273274> ```text275> L[O] = 0276> L[X] = X O277> L[Y] = Y O278> L[A] = A X Y O279> L[B] = B Y X O280> ```281282Однако вычислить линеаризацию для класса C, наследующего от A и B, невозможно:283284```python285L[C] = C + merge(AXYO, BYXO, AB)286 = C + A + merge(XYO, BYXO, B)287 = C + A + B + merge(XYO, YXO)288```289290На этом этапе слить списки XYO и YXO нельзя: X находится в хвосте YXO, а Y – в хвосте XYO. Подходящих голов не остаётся, и алгоритм C3 останавливается. Python 2.3 выдаёт ошибку и отказывается создавать класс C.291292## Плохие порядки разрешения методов293294MRO считается *плохим*, если нарушает такие фундаментальные свойства, как порядок локального старшинства и монотонность. В этом разделе я покажу, что и MRO для классических классов, и MRO для классов нового стиля в Python 2.2 являются плохими.295296Начнём с порядка локального старшинства – это проще. Рассмотрим следующий пример:297298```python299>>> F=type('Food',(),{'remember2buy':'spam'})300>>> E=type('Eggs',(F,),{'remember2buy':'eggs'})301>>> G=type('GoodFood',(F,E),{}) # В Python 2.3 это ошибка!302```303304с диаграммой наследования305306> ```text307> O308> |309> (buy spam) F310> | \311> | E (buy eggs)312> | /313> G314>315> (buy eggs or spam ?)316> ```317318Мы видим, что класс G наследует от F и E, причём F *перед* E. Поэтому можно ожидать, что атрибут *G.remember2buy* унаследуется от *F.remember2buy*, а не от *E.remember2buy*. Однако Python 2.2 выдаёт319320```python321>>> G.remember2buy322'eggs'323```324325Это нарушение порядка локального старшинства, поскольку порядок в списке локальных предшественников (то есть список родителей G) не сохраняется в линеаризации Python 2.2 для G:326327```python328L[G,P22]= G E F object # F *следует за* E329```330331Можно возразить, что F идёт после E в линеаризации Python 2.2, потому что F менее специализирован, чем E (F – суперкласс E). Тем не менее нарушение порядка локального старшинства совершенно неинтуитивно и чревато ошибками. Особенно это заметно в сравнении с классическими классами:332333```python334>>> class F: remember2buy='spam'335>>> class E(F): remember2buy='eggs'336>>> class G(F,E): pass337>>> G.remember2buy338'spam'339```340341В этом случае MRO – GFEF, и порядок локального старшинства сохраняется.342343Как общее правило, иерархий, подобных предыдущей, следует избегать, поскольку непонятно, должен ли F переопределять E или наоборот. Python 2.3 решает эту неоднозначность – при создании класса G возникает исключение, что фактически не позволяет программисту создавать двусмысленные иерархии. Причина в том, что алгоритм C3 не может выполнить слияние:344345```python346merge(FO,EFO,FE)347```348349не может быть выполнено, так как F находится в хвосте EFO, а E – в хвосте FE.350351Настоящее решение – спроектировать недвусмысленную иерархию, то есть наследовать G от E и F (сначала более конкретный), а не от F и E. В этом случае MRO будет GEF – без всяких сомнений.352353> ```text354> O355> |356> F (spam)357> / |358> (eggs) E |359> \ |360> G361> (eggs, no doubt)362> ```363364Python 2.3 вынуждает программиста писать хорошие иерархии (или, по крайней мере, менее подверженные ошибкам).365366Кстати, стоит отметить, что алгоритм Python 2.3 достаточно умён, чтобы распознавать очевидные ошибки, например дублирование классов в списке родителей:367368```python369>>> class A(object): pass370>>> class C(A,A): pass # ошибка371Traceback (most recent call last):372 File "<stdin>", line 1, in ?373TypeError: duplicate base class A374```375376Python 2.2 (как для классических классов, так и для классов нового стиля) в такой ситуации не выдавал бы никакого исключения.377378Наконец, хотелось бы выделить два урока, которые можно извлечь из этого примера:3793801. несмотря на название, MRO определяет порядок разрешения атрибутов, а не только методов;3812. обычная еда для питонистов – spam! (но вы и так это знали ;-)382383Обсудив проблему порядка локального старшинства, перейдём теперь к монотонности. Моя цель – показать, что ни MRO для классических классов, ни MRO для классов нового стиля в Python 2.2 не являются монотонными.384385Доказать немонотонность MRO для классических классов довольно тривиально – достаточно взглянуть на ромбовидную диаграмму:386387> ```text388> C389> / \390> / \391> A B392> \ /393> \ /394> D395> ```396397Несоответствие легко заметить:398399```python400L[B,P21] = B C # B предшествует C : методы B побеждают401L[D,P21] = D A C B C # B следует за C : методы C побеждают!402```403404С другой стороны, с MRO Python 2.2 и 2.3 проблем нет – оба дают:405406```python407L[D] = D A B C408```409410Гвидо отмечает в своей статье [\[3\]](https://python-all.ru/3.14/howto/mro.html#id6), что классический MRO на практике не так уж плох, поскольку для классических классов ромбов обычно удаётся избегать. Но все классы нового стиля наследуют от `object`, поэтому ромбы неизбежны, и несоответствия проявляются в любом графе множественного наследования.411412MRO Python 2.2 делает нарушение монотонности маловероятным, но возможным. Следующий пример, изначально предложенный Самуэле Педрони, показывает, что MRO Python 2.2 немонотонен:413414```python415>>> class A(object): pass416>>> class B(object): pass417>>> class C(object): pass418>>> class D(object): pass419>>> class E(object): pass420>>> class K1(A,B,C): pass421>>> class K2(D,B,E): pass422>>> class K3(D,A): pass423>>> class Z(K1,K2,K3): pass424```425426Вот линеаризации по C3 MRO (читателю предлагается проверить их самостоятельно и начертить диаграмму наследования ;-)427428```python429L[A] = A O430L[B] = B O431L[C] = C O432L[D] = D O433L[E] = E O434L[K1]= K1 A B C O435L[K2]= K2 D B E O436L[K3]= K3 D A O437L[Z] = Z K1 K2 K3 D A B C E O438```439440Python 2.2 даёт те же линеаризации для A, B, C, D, E, K1, K2 и K3, но другую линеаризацию для Z:441442```python443L[Z,P22] = Z K1 K3 A K2 D B C E O444```445446Понятно, что эта линеаризация *неверна*, поскольку A идёт перед D, тогда как в линеаризации K3 A идёт *после* D. Иными словами, в K3 методы, унаследованные от D, переопределяют методы от A, а в Z, который всё ещё является подклассом K3, методы от A переопределяют методы от D! Это нарушение монотонности. Более того, линеаризация Z в Python 2.2 также противоречит порядку локального старшинства: список локальных предшественников класса Z – \[K1, K2, K3\] (K2 предшествует K3), а в линеаризации Z K2 *следует за* K3. Эти проблемы объясняют, почему правило 2.2 было отброшено в пользу правила C3.447448## Конец449450Этот раздел – для нетерпеливого читателя, который пропустил все предыдущие разделы и прыгнул сразу в конец. Он также для ленивого программиста, который не хотел напрягать мозги. Наконец, он для программиста с некоторым самомнением – иначе бы он не читал статью о порядке разрешения методов C3 при множественном наследовании ;-) Все три добродетели вместе (и *не* по отдельности) заслуживают награды: награда – короткий скрипт на Python 2.2, который позволяет вычислить MRO 2.3 без риска для мозга. Просто измените последнюю строку, чтобы поиграть с различными примерами, которые я обсуждал в этой статье.:451452```python453#<mro.py>454455"""Алгоритм C3 от Самуэле Педрони (с улучшенной мной читаемостью)."""456457class __metaclass__(type):458 "All classes are metamagically modified to be nicely printed"459 __repr__ = lambda cls: cls.__name__460461class ex_2:462 "Serious order disagreement" #От Гвидо463 class O: pass464 class X(O): pass465 class Y(O): pass466 class A(X,Y): pass467 class B(Y,X): pass468 try:469 class Z(A,B): pass #создаёт Z(A,B) в Python 2.2470 except TypeError:471 pass # Z(A,B) невозможно создать в Python 2.3472473class ex_5:474 "My first example"475 class O: pass476 class F(O): pass477 class E(O): pass478 class D(O): pass479 class C(D,F): pass480 class B(D,E): pass481 class A(B,C): pass482483class ex_6:484 "My second example"485 class O: pass486 class F(O): pass487 class E(O): pass488 class D(O): pass489 class C(D,F): pass490 class B(E,D): pass491 class A(B,C): pass492493class ex_9:494 "Difference between Python 2.2 MRO and C3" #От Самуэле495 class O: pass496 class A(O): pass497 class B(O): pass498 class C(O): pass499 class D(O): pass500 class E(O): pass501 class K1(A,B,C): pass502 class K2(D,B,E): pass503 class K3(D,A): pass504 class Z(K1,K2,K3): pass505506def merge(seqs):507 print '\n\nCPL[%s]=%s' % (seqs[0][0],seqs),508 res = []; i=0509 while 1:510 nonemptyseqs=[seq for seq in seqs if seq]511 if not nonemptyseqs: return res512 i+=1; print '\n',i,'round: candidates...',513 for seq in nonemptyseqs: # поиск кандидатов на слияние среди голов последовательностей514 cand = seq[0]; print ' ',cand,515 nothead=[s for s in nonemptyseqs if cand in s[1:]]516 if nothead: cand=None #отклонить кандидата517 else: break518 if not cand: raise "Inconsistent hierarchy"519 res.append(cand)520 for seq in nonemptyseqs: # удалить кандидата521 if seq[0] == cand: del seq[0]522523def mro(C):524 "Compute the class precedence list (mro) according to C3"525 return merge([[C]]+map(mro,C.__bases__)+[list(C.__bases__)])526527def print_mro(C):528 print '\nMRO[%s]=%s' % (C,mro(C))529 print '\nP22 MRO[%s]=%s' % (C,C.mro())530531print_mro(ex_9.Z)532533#</mro.py>534```535536Ну вот и всё, народ,537538> Наслаждайтесь!539540## Ресурсы541542\[[1](https://python-all.ru/3.14/howto/mro.html#id1)\]543544Обсуждение в python-dev, начатое Самуэле Педрони: [https://mail.python.org/pipermail/python-dev/2002-October/029035.html](https://python-all.ru/3.14/howto/mro.html)545546\[[2](https://python-all.ru/3.14/howto/mro.html#id2)\]547548Статья *A Monotonic Superclass Linearization for Dylan*: [https://doi.org/10.1145/236337.236343](https://python-all.ru/3.14/howto/mro.html)549550\[[3](https://python-all.ru/3.14/howto/mro.html#id3)\]551552Эссе Гвидо ван Россума *Unifying types and classes in Python 2.2*: [https://web.archive.org/web/20140210194412/http://www.python.org/download/releases/2.2.2/descrintro](https://python-all.ru/3.14/howto/mro.html)553