Как работает super () 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).

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"

должны ThirdMRO 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")

на 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().

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

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

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

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

  5. после super(First, self).__init__() завершено, "первый" напечатанный.

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

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

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

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


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


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

здесь A является расширяемым базовым классом и B и C являются классами MixIn, которые предоставляют функцию f. A и B оба параметра expect 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. Внутренние работы это Linerization С3 родословной классы. Прочитать все об этом здесь: 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

вызов третьего () находит init определено в третьем. И вызов супер в этой рутине вызывает init определена в первую очередь. MRO=[первый, второй]. Теперь позвоните в super in 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"