Как работает 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 it"

То, что я не получаю: будет ли класс Third() наследовать оба метода конструктора? Если да, то какой из них будет запущен с помощью super() и почему?

А что, если вы хотите запустить другой? Я знаю, что это имеет какое-то отношение к порядку разрешения метода Python (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 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. Они не предназначены для печати "первая\третья\третья" или что-то еще. Вы можете - и, конечно, должны поэкспериментировать с примером, добавить вызовы 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")

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

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

>>> Third()
second
first
third

Ответ 3

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

Вам необходимо понять, что super(MyClass, self).__init__() предоставляет следующий метод __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().

  1. Согласно MRO Third.__init__ выполняет.
    • печатает Third(): entering
    • затем выполняется super(Third, self).__init__() и MRO возвращает вызванный First.__init__.
  2. First.__init__ выполняется.
    • печатает First(): entering
    • затем выполняется super(First, self).__init__() и MRO возвращает вызванный Second.__init__.
  3. Second.__init__ выполняется.
    • печатает Second(): entering
    • затем выполняется super(Second, self).__init__() и MRO возвращает вызванный object.__init__.
  4. object.__init__ выполняется (в коде нет операторов печати)
  5. выполнение возвращается к Second.__init__, который затем печатает Second(): exiting
  6. выполнение возвращается к First.__init__, который затем печатает First(): exiting
  7. выполнение возвращается к Third.__init__, который затем печатает Third(): exiting

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

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

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

Ответ 4

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

Ответ 5

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

Здесь 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)

Ответ 6

Я понимаю, что это напрямую не отвечает на вопрос 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 __init__() не будет вызван.

Ответ 7

В общем и целом

Предполагая, что все сходит с 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

Ответ 8

О комментарий @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, когда они переопределяют метод, в то время как это одно из немногих действительно полезных и разумных способов использования этих "магических переменных".

Ответ 9

Еще одна не охваченная точка - это параметры для инициализации классов. Поскольку пункт назначения 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 может быть вызван несколько раз, в зависимости от о реализации.

В заключение: совместное наследование и супер и конкретные параметры для инициализации не работают вместе очень хорошо.

Ответ 10

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 it"

t = Third()

Выход

first 10
second 20
that it

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

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

first 10
that it

Ответ 11

В learningpyhonthehardway я изучаю нечто, называемое super() встроенной функцией, если не ошибаюсь. Вызов функции super() может помочь наследованию пройти через родителя и "братьев и сестер" и помочь вам увидеть яснее. Я все еще новичок, но я люблю делиться своим опытом использования этого super() в python2.7.

Если вы прочитали комментарии на этой странице, вы услышите о Порядке разрешения методов (MRO), метод, который является функцией, которую вы написали, MRO будет использовать схему Depth-First-слева-направо для поиска и запуска. Вы можете сделать больше исследований по этому вопросу.

Добавляя функцию super()

super(First, self).__init__() #example for class First.

Вы можете соединить несколько экземпляров и "семей" с помощью super(), добавив в них все и каждого из них. И он будет выполнять методы, пройти их и убедиться, что вы не пропустили! Однако, добавив их до или после, вы узнаете, выполнили ли вы упражнение learningpythonthehardway 44. Пусть начнется веселье !!

Принимая пример ниже, вы можете скопировать и вставить и попробовать запустить его:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

Как это работает? Экземпляр five() будет выглядеть следующим образом. Каждый шаг идет от класса к классу, где добавлена супер функция.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

Родитель был найден, и он будет продолжаться до третьего и четвертого !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

Теперь все классы с super() были доступны! Родительский класс был найден и выполнен, и теперь он продолжает распаковывать функцию в наследствах для завершения кодов.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

Результат программы выше:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

Для меня добавление super() позволяет мне увидеть, как python будет выполнять мое кодирование, и убедиться, что наследование может получить доступ к методу, который я намеревался.

Ответ 12

Я хотел бы добавить, что говорит @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 it"

становится очевидным, как этот метод будет разрешен

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that it"

Ответ 13

Может быть, есть еще кое-что, что можно добавить, небольшой пример с Django rest_framework и декораторами. Это дает ответ на скрытый вопрос: "зачем мне это все равно?"

Как уже было сказано: мы с Django rest_framework, и мы используем общие представления, и для каждого типа объектов в нашей базе данных мы находимся с одним классом представления, предоставляющим GET и POST для списков объектов, и другим классом представления, предоставляющим GET, PUT и DELETE для отдельных объектов.

Теперь POST, PUT и DELETE мы хотим украсить Django login_required. Обратите внимание, как это касается обоих классов, но не всех методов в обоих классах.

Решение может пройти множественное наследование.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Аналогично для других методов.

В списке наследования моих конкретных классов я добавил бы свой LoginToPost перед ListCreateAPIView и LoginToPutOrDelete перед RetrieveUpdateDestroyAPIView. Мои конкретные классы get бы остаться без отделки.

Ответ 14

В Python 3. Наследование 5+ выглядит предсказуемым и очень приятным для меня. Пожалуйста, посмотрите на этот код:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Выходы:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

Как видите, он вызывает foo ровно ОДНО время для каждой унаследованной цепочки в том же порядке, в котором она была унаследована. Вы можете получить этот заказ, позвонив .mro :

Четвертый → Третий → Первый → Второй → База → объект