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

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, 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().

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

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

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

  4. внутри __init__ второй 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 далее, чтобы хорошо работать в сложных случаях, но я думаю, что использование "глубина-первый слева направо обход" + "удаление дубликатов ожидать для последнего" по-прежнему работает в большинстве случаев (пожалуйста, прокомментируйте, если это не так). Обязательно прочитайте сообщение в блоге Гвидо!

Это называется Проблема, на странице есть запись на 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/

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

  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=[первый, второй]. Звоните прямо сейчас, чтобы супер в 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"