Как работает Python 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() класс наследует оба метода конструктора? Если да, то какой из них будет работать с супер() и почему?
и что если вы хотите запустить другой? Я знаю, что в нем что-то есть чтобы сделать с порядком разрешения метода Python (MRO).
11 ответов:
это подробно с разумным количеством деталей самим Гвидо в своем блоге Порядок Разрешения Метода (включая две предыдущие попытки).
в вашем примере
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"должны
ThirdS MRO be[First, Second]или[Second, First]? Нет очевидного ожидания, и Python вызовет ошибку:TypeError: Error when calling the metaclass bases Cannot create a consistent method resolution order (MRO) for bases Second, FirstEdit: я вижу, что несколько человек утверждают, что примеров выше хватает
super()вызовы, поэтому позвольте мне объяснить: смысл примеров-показать, как строится MRO. Они не предназначен для печати "first\nsecond\third" или что-то еще. Вы можете – и должны, конечно, поиграть с например, добавитьsuper()вызовы, посмотреть, что происходит, и получить более глубокое понимание модели наследования Python. Но моя цель здесь - сохранить его простым и показать, как строится MRO. И он построен так, как я объяснил:>>> Fourth.__mro__ (<class '__main__.Fourth'>, <class '__main__.Second'>, <class '__main__.Third'>, <class '__main__.First'>, <type 'object'>)
ваш код, и другие ответы, все глючит. Им не хватает
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")The
super()вызов находит следующий метод в MRO на каждом шаге, поэтому первый и Второй должны иметь его тоже, иначе выполнение останавливается в концеSecond.__init__().вот что я получил:
>>> Third() second first third
я хотел уточнить ответ неживыми немного, потому что, когда я начал читать о том, как использовать super() в иерархии множественного наследования в Python, я не получил его сразу.
то, что вам нужно понять, это
super(MyClass, self).__init__()предоставляет далее__init__метод в соответствии с используемым алгоритмом упорядочения разрешения метода (MRO)в контексте полной иерархии наследования.это последняя часть очень важно понять. Давайте рассмотрим пример еще раз:
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 "that's it"согласно этой статье О порядке разрешения метода Гвидо ван Россум, приказ разрешить
__init__вычисляется (до Python 2.3) с использованием "глубины первого обхода слева направо":Third --> First --> object --> Second --> objectпосле удаления всех дубликатов, кроме последнего, мы получаем:
Third --> First --> Second --> objectИтак, давайте проследим, что происходит, когда мы создаем экземпляр
Thirdкласс, например,x = Third().
согласно MRO
__init__третий называется первым.далее, согласно МРО, внутри
__init__методsuper(Third, self).__init__()разрешает__init__Метод первый, который его вызывают.внутри
__init__первогоsuper(First, self).__init__()называет__init__на во-вторых, потому что МРО диктует!внутри
__init__второйsuper(Second, self).__init__()вызовы элемент__init__объекта, который ничего не значит. После этого "второй" печатается.после
super(First, self).__init__()завершено, "первый" напечатанный.после
super(Third, self).__init__()завершено, " вот и все " печатается.это подробно объясняет, почему создание экземпляра Third () приводит к:
>>> x = Third() second first that's itалгоритм MRO был улучшен из Python 2.3 далее, чтобы хорошо работать в сложных случаях, но я думаю, что использование "глубина-первый слева направо обход" + "удаление дубликатов ожидать для последнего" по-прежнему работает в большинстве случаев (пожалуйста, прокомментируйте, если это не так). Обязательно прочитайте сообщение в блоге Гвидо!
Это называется Проблема, на странице есть запись на Python, но, короче говоря, Python будет вызывать методы суперкласса слева направо.
это к тому, как я решил проблему наличия множественного наследования с разными переменными для инициализации и наличия нескольких миксинов с одним и тем же вызовом функции. Я должен явно добавить переменные прошло **kwargs и добавить подмешать интерфейс, который будет конечной точкой для супер звонки.
здесь
Aявляется расширяемым базовым классом иBиCявляются mixin классы оба, которые обеспечивают функциюf.AиBКак рассчитывают параметрvв их__init__иCждетw. Функцияfпринимает один параметрy.Qнаследует от всех трех классов.MixInFэто интерфейс mixin дляBиC.class A(object): def __init__(self, v, *args, **kwargs): print "A:init:v[{0}]".format(v) kwargs['v']=v super(A, self).__init__(*args, **kwargs) self.v = v class MixInF(object): def __init__(self, *args, **kwargs): print "IObject:init" def f(self, y): print "IObject:y[{0}]".format(y) class B(MixInF): def __init__(self, v, *args, **kwargs): print "B:init:v[{0}]".format(v) kwargs['v']=v super(B, self).__init__(*args, **kwargs) self.v = v def f(self, y): print "B:f:v[{0}]:y[{1}]".format(self.v, y) super(B, self).f(y) class C(MixInF): def __init__(self, w, *args, **kwargs): print "C:init:w[{0}]".format(w) kwargs['w']=w super(C, self).__init__(*args, **kwargs) self.w = w def f(self, y): print "C:f:w[{0}]:y[{1}]".format(self.w, y) super(C, self).f(y) class Q(C,B,A): def __init__(self, v, w): super(Q, self).__init__(v=v, w=w) def f(self, y): print "Q:f:y[{0}]".format(y) super(Q, self).f(y)
Я понимаю, что это прямо не отвечает
super()вопрос, но я чувствую, что это достаточно важно, чтобы поделиться.существует также способ прямого вызова каждого унаследованного класса:
class First(object): def __init__(self): print '1' class Second(object): def __init__(self): print '2' class Third(First, Second): def __init__(self): Second.__init__(self)просто обратите внимание, что если вы сделаете это таким образом, вам придется вызывать каждый вручную, как я уверен
First' s__init__()не назовешь.
о комментарий@calfzhou, вы можете использовать, как обычно,
**kwargs:class A(object): def __init__(self, a, *args, **kwargs): print("A", a) class B(A): def __init__(self, b, *args, **kwargs): super(B, self).__init__(*args, **kwargs) print("B", b) class A1(A): def __init__(self, a1, *args, **kwargs): super(A1, self).__init__(*args, **kwargs) print("A1", a1) class B1(A1, B): def __init__(self, b1, *args, **kwargs): super(B1, self).__init__(*args, **kwargs) print("B1", b1) B1(a1=6, b1=5, b="hello", a=None)результат:
A None B hello A1 6 B1 5вы также можете использовать их позиционно:
B1(5, 6, b="hello", a=None)но вы должны помнить MRO, это действительно сбивает с толку.
Я может быть немного раздражает, но я заметил, что люди забыли, каждый раз использовать
*argsи**kwargsкогда они переопределяют метод, в то время как это один из немногих действительно полезное и разумное использование этих "магических переменных".
еще одна не охваченная точка-это передача параметров для инициализации классов. С момента назначения
superзависит от подкласса единственный хороший способ передать параметры упаковка их все вместе. Тогда будьте осторожны, чтобы не иметь то же имя параметра с разными значениями.пример:
class A(object): def __init__(self, **kwargs): print('A.__init__') super().__init__() class B(A): def __init__(self, **kwargs): print('B.__init__ {}'.format(kwargs['x'])) super().__init__(**kwargs) class C(A): def __init__(self, **kwargs): print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b'])) super().__init__(**kwargs) class D(B, C): # MRO=D, B, C, A def __init__(self): print('D.__init__') super().__init__(a=1, b=2, x=3) print(D.mro()) D()выдает:
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] D.__init__ B.__init__ 3 C.__init__ with 1, 2 A.__init__вызов суперкласса
__init__непосредственно к более прямому назначению параметров заманчиво, но терпит неудачу, если есть любойsuperвызов суперкласса и / или MRO изменяется, и класс A может вызываться несколько раз, в зависимости от реализации.в заключение: совместное наследование и супер и конкретные параметры для инициализации не очень хорошо работают вместе.
в целом
предполагая, что все происходит от
object(вы сами по себе, если это не так), Python вычисляет порядок разрешения метода (MRO) на основе вашего дерева наследования классов. MRO удовлетворяет 3 свойства:
- дети класса приходят раньше своих родителей
- левые родители приходят перед правыми родителями
- класс появляется только один раз в MRO
если такой порядок не существует, ошибки Python. Внутренняя работа этого-Линеризация C3 родословной классов. Прочитать все об этом здесь: https://www.python.org/download/releases/2.3/mro/
таким образом, в обоих примерах, приведенных ниже, это:
- ребенок
- левый
- право
- родитель
когда вызывается метод, первым вхождением этого метода в MRO является тот, который вызывается. Любой класс, который не реализует этот метод пропускается. Любой звонок в
superв этом методе будет вызывать следующее вхождение этого метода в MRO. Следовательно, важно как то, в каком порядке вы размещаете классы в наследовании, так и где вы помещаете вызовыsuperв методах.С
superпервый в каждом методеclass Parent(object): def __init__(self): super(Parent, self).__init__() print "parent" class Left(Parent): def __init__(self): super(Left, self).__init__() print "left" class Right(Parent): def __init__(self): super(Right, self).__init__() print "right" class Child(Left, Right): def __init__(self): super(Child, self).__init__() print "child"
Child()выходы:parent right left childС
superпоследний в каждом методеclass Parent(object): def __init__(self): print "parent" super(Parent, self).__init__() class Left(Parent): def __init__(self): print "left" super(Left, self).__init__() class Right(Parent): def __init__(self): print "right" super(Right, self).__init__() class Child(Left, Right): def __init__(self): print "child" super(Child, self).__init__()
Child()выходы:child left right parent
class First(object): def __init__(self, a): print "first", a super(First, self).__init__(20) class Second(object): def __init__(self, a): print "second", a super(Second, self).__init__() class Third(First, Second): def __init__(self): super(Third, self).__init__(10) print "that's it" t = Third()выход
first 10 second 20 that's itвызов третьего() находит init определен в третий. И вызов супер в этой рутине вызывает init определена в первую очередь. MRO=[первый, второй]. Звоните прямо сейчас, чтобы супер в init определенный в первом продолжит поиск MRO и найдет init определяется во втором, и любой вызов super ударит по объекту по умолчанию init. Я надеюсь, что этот пример проясняет концепцию.
Если ты не звонишь Супер С самого начала. Цепь останавливается, и вы получите следующий результат.
first 10 that's it
Я хотел бы добавить к что говорит @Visionscaper вверху:
Third --> First --> object --> Second --> objectв этом случае интерпретатор не отфильтровывает класс объекта, потому что он дублируется, а потому что второй появляется в позиции головы и не появляется в позиции хвоста в подмножестве иерархии. В то время как объект появляется только в хвостовых положениях и не считается сильной позицией в алгоритме C3 для определения приоритета.
линеаризация (mro) класса C, L(C), это
- Класс C
- плюс слияние
- линеаризация его родителей P1, P2,.. = L(P1, P2,...) и
- список его родителей P1, P2,..
Линеаризованное слияние выполняется путем выбора общих классов, которые отображаются в качестве главы списков, а не хвоста, поскольку порядок имеет значение (станет ясно ниже)
линеаризация Третьего может быть вычислена как следует:
L(O) := [O] // the linearization(mro) of O(object), because O has no parents L(First) := [First] + merge(L(O), [O]) = [First] + merge([O], [O]) = [First, O] // Similarly, L(Second) := [Second, O] L(Third) := [Third] + merge(L(First), L(Second), [First, Second]) = [Third] + merge([First, O], [Second, O], [First, Second]) // class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists // class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, = [Third, First] + merge([O], [Second, O], [Second]) // class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3 = [Third, First, Second] + merge([O], [O]) = [Third, First, Second, O]таким образом, для реализации 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 "that's it"становится очевидным, как этот метод будет решен
Third.__init__() ---> First.__init__() ---> Second.__init__() ---> Object.__init__() ---> returns ---> Second.__init__() - prints "second" - returns ---> First.__init__() - prints "first" - returns ---> Third.__init__() - prints "that's it"