Как работает 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"
должны
Third
S 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, First
Edit: я вижу, что несколько человек утверждают, что примеров выше хватает
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"