Как Python super () работает с множественным наследованием? - PullRequest
761 голосов
/ 19 июля 2010

Я довольно новичок в объектно-ориентированном программировании на 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() будет ли класс наследовать оба метода конструктора? Если да, то какой из них будет работать с super () и почему?

А что, если вы хотите запустить другой? Я знаю, что это как-то связано с порядком разрешения методов Python ( MRO ).

Ответы [ 13 ]

622 голосов
/ 19 июля 2010

Это подробно и с достаточным количеством подробностей сам Гвидо в своем сообщении в блоге Порядок разрешения методов (включая две более ранние попытки).

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

Edit: Я вижу, как некоторые люди утверждают, что в приведенных выше примерах отсутствуют вызовы super(), поэтому позвольте мне объяснить:смысл примеров в том, чтобы показать, как строится MRO.Они не предназначены для печати "first \ nsecond \ третий" или что-то еще.Вы можете - и, конечно, должны поиграться с этим примером, добавить вызовы super(), посмотреть, что произойдет, и глубже понять модель наследования Python.Но моя цель здесь состоит в том, чтобы сделать это простым и показать, как строится MRO.И он построен, как я объяснил:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
221 голосов
/ 01 мая 2013

Ваш код и другие ответы содержат ошибки.Они пропускают вызовы 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 на каждом шаге, поэтому у First и Second его тоже должно быть, в противном случае выполнение останавливается в конце Second.__init__().

Вот что я получаю:

>>> Third()
second
first
third
157 голосов
/ 12 мая 2015

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

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

50 голосов
/ 19 июля 2010

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

25 голосов
/ 17 августа 2014

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

Здесь A - расширяемый базовый класс, а B и C - классы MixIn.оба, которые обеспечивают функцию f.A и B оба ожидают параметра v в своих __init__, а C ожидают w.Функция f принимает один параметр y.Q наследуется от всех трех классов.MixInF - это смешанный интерфейс для 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)
19 голосов
/ 18 сентября 2017

В целом

Предполагая, что все происходит от object (если вы этого не делаете), Python вычисляет порядок разрешения методов (MRO) на основе вашего дерева наследования классов.MRO удовлетворяет 3 свойствам:

  • Дети класса идут раньше своих родителей
  • Левые родители приходят раньше правых родителей
  • Класс появляется только один раз в MRO

Если такого упорядочения не существует, ошибки Python.Внутренняя работа этого - линеаризация C3 родословной классов.Прочитайте все об этом здесь: https://www.python.org/download/releases/2.3/mro/

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

  1. Child
  2. Left
  3. Right
  4. Parent

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

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

16 голосов
/ 22 апреля 2016

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

11 голосов
/ 29 июля 2014

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

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

4 голосов
/ 10 декабря 2015
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 , определенный в Third. И вызов super в этой подпрограмме вызывает init , определенный в First. MRO = [Первый, Второй]. Теперь вызов super в init , определенный в First, продолжит поиск MRO и найдет init , определенный в Second, и любой вызов super попадет в объект по умолчанию init . Я надеюсь, что этот пример проясняет концепцию.

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

first 10
that's it
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...