Как функция super () в Python работает с множественным наследованием?
Как super()
работает с множественным наследованием? Например, учитывая:
class First(object):
def __init__(self):
print "first"
class Second(object):
def __init__(self):
print "second"
class Third(First, Second):
def __init__(self):
super(Third, self).__init__()
print "that's it"
К какому родительскому методу Third
относится super().__init__
? Могу ли я выбрать, какой из них выполняется?
Я знаю, что это как-то связано с порядком разрешения метода (MRO).
Переведено автоматически
Ответ 1
Это описано с достаточным количеством деталей самим Гвидо в его сообщении в блоге Порядок разрешения метода (включая две предыдущие попытки).
В вашем примере Third()
вызовет First.__init__
. Python ищет каждый атрибут в родительском классе, поскольку они перечислены слева направо. В данном случае мы ищем __init__
. Итак, если вы определяете
class Third(First, Second):
...
Python начнет с поиска в First
, и, если у First
нет атрибута, то он будет искать в Second
.
Эта ситуация усложняется, когда пути наследования начинают пересекаться (например, если First
унаследовано от Second
). Прочитайте ссылку выше для получения более подробной информации, но, в двух словах, Python попытается поддерживать порядок, в котором каждый класс отображается в списке наследования, начиная с самого дочернего класса.
Итак, например, если у вас было:
class First(object):
def __init__(self):
print "first"
class Second(First):
def __init__(self):
print "second"
class Third(First):
def __init__(self):
print "third"
class Fourth(Second, Third):
def __init__(self):
super(Fourth, self).__init__()
print "that's it"
MRO будет [Fourth, Second, Third, First].
Кстати: если Python не может найти согласованный порядок разрешения метода, он вызовет исключение, вместо того чтобы вернуться к поведению, которое может удивить пользователя.
Пример неоднозначного MRO:
class First(object):
def __init__(self):
print "first"
class Second(First):
def __init__(self):
print "second"
class Third(First, Second):
def __init__(self):
print "third"
Должно Third
быть MRO [First, Second]
или [Second, First]
? Очевидного ожидания нет, и Python выдаст ошибку:
TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution order (MRO) for bases Second, First
Почему в приведенных выше примерах отсутствуют super()
вызовы? Смысл примеров в том, чтобы показать, как строится MRO. Они не предназначены для печати "first\nsecond\third"
или чего-то еще. Вы можете – и должны, конечно, - поиграть с примером, добавить super()
вызовы, посмотреть, что получится, и получить более глубокое представление о модели наследования в Python. Но моя цель здесь - упростить и показать, как строится MRO. И он построен так, как я объяснил:
>>> Fourth.__mro__
(<class '__main__.Fourth'>,
<class '__main__.Second'>, <class '__main__.Third'>,
<class '__main__.First'>,
<type 'object'>)
Ответ 2
Ваш код и другие ответы содержат ошибки. В них отсутствуют super()
вызовы в первых двух классах, которые необходимы для работы совместного подкласса. Лучше, если:
class First(object):
def __init__(self):
super(First, self).__init__()
print("first")
class Second(object):
def __init__(self):
super(Second, self).__init__()
print("second")
class Third(First, Second):
def __init__(self):
super(Third, self).__init__()
print("third")
Вывод:
>>> Third()
second
first
third
super()
Вызов находит следующий метод в MRO на каждом шаге, поэтому First
и Second
он тоже должен быть, иначе выполнение останавливается в конце Second.__init__()
.
Без super()
вызовов в First
и Second
вывод отсутствует second
:
>>> Third()
first
third
Ответ 3
Я хотел немного уточнить ответ lifeless, потому что, когда я начал читать о том, как использовать super () в иерархии множественного наследования в Python, я не сразу понял это.
Что вам нужно понимать, так это то, что super(MyClass, self).__init__()
предоставляет метод next __init__
в соответствии с используемым алгоритмом упорядочения разрешения методов (MRO) в контексте полной иерархии наследования.
Эта последняя часть имеет решающее значение для понимания. Давайте еще раз рассмотрим пример:
#!/usr/bin/env python2
class First(object):
def __init__(self):
print "First(): entering"
super(First, self).__init__()
print "First(): exiting"
class Second(object):
def __init__(self):
print "Second(): entering"
super(Second, self).__init__()
print "Second(): exiting"
class Third(First, Second):
def __init__(self):
print "Third(): entering"
super(Third, self).__init__()
print "Third(): exiting"
Согласно этой статье о порядке разрешения метода, написанной Гвидо ван Россумом, порядок разрешения __init__
вычисляется (до Python 2.3) с использованием "обхода по глубине слева направо". :
Third --> First --> object --> Second --> object
После удаления всех дубликатов, за исключением последнего, мы получаем :
Third --> First --> Second --> object
Итак, давайте проследим, что происходит, когда мы создаем экземпляр Third
класса, например, x = Third()
.
- Согласно MRO
Third.__init__
выполняется.- С принтами
Third(): entering
- затем
super(Third, self).__init__()
выполняется и возвращается MRO,First.__init__
который вызывается.
- С принтами
First.__init__
выполняется.- С принтами
First(): entering
- затем
super(First, self).__init__()
выполняется и возвращается MRO,Second.__init__
который вызывается.
- С принтами
Second.__init__
выполняется.- С принтами
Second(): entering
- затем
super(Second, self).__init__()
выполняется и возвращается MRO,object.__init__
который вызывается.
- С принтами
object.__init__
выполняется (в коде нет инструкций print)- выполнение возвращается к
Second.__init__
, который затем выводитSecond(): exiting
- выполнение возвращается к
First.__init__
, который затем выводитFirst(): exiting
- выполнение возвращается к
Third.__init__
, который затем выводитThird(): exiting
Здесь подробно объясняется, почему создание экземпляра Third() приводит к :
Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting
Алгоритм MRO был улучшен начиная с Python 2.3, чтобы хорошо работать в сложных случаях, но я предполагаю, что использование "обхода слева направо в глубину" + "ожидаемое удаление дубликатов последним" все еще работает в большинстве случаев (пожалуйста, прокомментируйте, если это не так). Обязательно прочтите сообщение в блоге Guido!
Ответ 4
Это известно как проблема с бриллиантами, на странице есть запись на Python, но, короче говоря, Python будет вызывать методы суперкласса слева направо.