Как работает супер () Python с множественным наследованием?

Я очень много нового в объектно-ориентированном программировании на 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 ).

  • Поиск мертвого кода в большом проекте python
  • Внедрение системы несвязанных наборов в Python
  • Как проверить связь blas / lapack в numpy / scipy?
  • Failed scipy.special import "Символ не найден: ___addtf3"
  • Относительная ошибка импорта с py2exe
  • Переустановка python на Mac OS 10.6 с другой версией gcc
  • Обновление координат графиков в matplotlib
  • Как вставить Python3 со стандартной библиотекой
  • 11 Solutions collect form web for “Как работает супер () Python с множественным наследованием?”

    Это подробно описано с достаточным количеством подробностей самим Гвидо по адресу http://python-history.blogspot.com/2010/06/method-resolution-order.html (включая две предыдущие попытки).

    В вашем примере 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" 

    Должен ли Третий MRO быть [First, Second] или [ Second, First] ? Нет очевидного ожидания, и Python вызовет ошибку:

    TypeError: Ошибка при вызове баз метакласса Невозможно создать согласованный порядок разрешения метода (MRO) для баз 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("that's it") 

    Вызов super () находит на каждом шаге метод / next / в MRO, поэтому First и Second тоже должны иметь его, иначе выполнение останавливается в конце Second.__init__ .

    Это то, что я получаю:

     >>> Third() second first that's it 

    Я хотел немного выработать ответ, потому что, когда я начал читать о том, как использовать super () в иерархии множественного наследования в Python, я не получил его немедленно.

    Что вам нужно понять, так это то, что super(MyClass, self).__init__() предоставляет следующий метод __init__ соответствии с используемым алгоритмом Order Resolution Ordering (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" 

    Согласно этой статье о порядке разрешения порядка от Guido van Rossum, порядок разрешения __init__ рассчитывается (до Python 2.3) с использованием «пересечения по глубине слева направо»:

     Third --> First --> object --> Second --> object 

    После удаления всех дубликатов, кроме последнего, мы получаем:

     Third --> First --> Second --> object 

    Итак, давайте следить за тем, что происходит, когда мы создаем экземпляр Third класса, например x = Third() .

    1. Согласно MRO __init__ of Third называется первым.

    2. Далее, согласно MRO, внутри метода __init__ super(Third, self).__init__() разрешает метод __init__ First, который вызывается.

    3. Внутри __init__ of First super(First, self).__init__() вызывает __init__ of Second, потому что это то, что диктует MRO!

    4. Внутри __init__ Second super(Second, self).__init__() вызывает объект __init__ , который не имеет значения. После этого печатается «вторая» .

    5. После super(First, self).__init__() завершено, «первая» печатается .

    6. После super(Third, self).__init__() завершено, «вот оно» печатается .

    Это объясняет, почему создание экземпляра Third () приводит к:

     >>> x = Third() second first that's it 

    Алгоритм MRO был улучшен с Python 2.3, чтобы хорошо работать в сложных случаях, но я предполагаю, что использование в большинстве случаев «перемотки слева направо» + «удаление дубликатов для последнего» все еще работает в большинстве случаев (пожалуйста если это не так). Обязательно прочитайте сообщение в блоге от Guido!

    Это называется проблемой Diamond , страница имеет запись на Python, но, коротко, Python будет называть методы суперкласса слева направо.

    Это касается того, как я решил выдать множественное наследование с разными переменными для инициализации и иметь несколько MixIns с тем же вызовом функции. Мне пришлось явно добавлять переменные в переданные ** kwargs и добавлять интерфейс MixIn в качестве конечной точки для супервызов.

    Здесь A – расширяемый базовый класс, а B и C – классы MixIn, которые предоставляют функцию f . A и B оба ожидают параметр v в своих __init__ и C ожидает w . Функция f принимает один параметр y . Q наследуется от всех трех классов. MixInF – это интерфейс mixin для B и C

    • IPython NoteBook этого кода
    • Github Repo с примером кода
     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) 

    О комментарии @ 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 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)
    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) 

    Просто имейте в виду, что если вы сделаете это так, вам придется вызывать их вручную, так как я уверен, что первая __init__() не будет вызвана.

    Другой еще не охваченной точкой является прохождение параметров для инициализации классов. Поскольку назначение 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/

    Таким образом, в обоих примерах, приведенных ниже, это:

    1. ребенок
    2. Оставил
    3. Правильно
    4. родитель

    Когда вызывается метод, первое вхождение этого метода в 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 

    Call to Third () определяет init, определенный в третьем. И вызов super в этой процедуре вызывает init, определенный в First. MRO = [Первый, Второй]. Теперь вызов super в init, определенный в First, продолжит поиск MRO и найдет init, определенный во втором, и любой вызов super ударит объект init по умолчанию. Надеюсь, этот пример прояснит концепцию.

    Если вы не называете super от First. Цепь останавливается, и вы получите следующий результат.

     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" 
    Python - лучший язык программирования в мире.