Как «супер» работает с мультинаследованием в Python? [Дубликат]

В то время как обещания и обратные вызовы хорошо работают во многих ситуациях, боль в задней части выражает нечто вроде:

if (!name) {
  name = async1();
}
async2(name);

. В итоге вы пройдете через async1; проверьте, не определено ли name или нет, и соответственно вызовите обратный вызов.

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

Хотя в в порядке хорошо , это раздражает, когда у вас много подобных случаев и обработка ошибок.

Fibers помогает в решении проблемы.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

Вы можете проверить проект здесь .

623
задан Peter O. 29 July 2014 в 21:39
поделиться

11 ответов

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

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

Если MF Third будет [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. Они не предназначены для печати «first\nsecond \ third» или что-то еще. Вы можете - и должны, конечно, поиграть с примером, добавить super() звонки, посмотреть, что произойдет, и получить более глубокое понимание модели наследования Python. Но моя цель состоит в том, чтобы держать его простым и показать, как строится MRO. И он построен, как я объяснил:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
511
ответ дан akaihola 28 August 2018 в 13:39
поделиться

Это касается того, как я решил выдать множественное наследование с разными переменными для инициализации и иметь несколько 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)
21
ответ дан brent.payne 28 August 2018 в 13:39
поделиться

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

Что вам нужно понять, так это то, что super(MyClass, self).__init__() предоставляет следующий метод next __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__ метод First, который вызывается.
  3. Внутри __init__ Первого super(First, self).__init__() вызывает __init__ из Во-вторых, потому что это то, что диктует MRO!
  4. Внутри __init__ of 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!

125
ответ дан Community 28 August 2018 в 13:39
поделиться

Ваш код и другие ответы являются ошибками. Они пропускают вызовы 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__().

Это то, что я get:

>>> Third()
second
first
third
181
ответ дан jojo 28 August 2018 в 13:39
поделиться

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

13
ответ дан Marco Sulla 28 August 2018 в 13:39
поделиться

Я хотел бы добавить к , что @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"
1
ответ дан Md. Abu Nafee Ibna Zahid 28 August 2018 в 13:39
поделиться

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

45
ответ дан Peter Mortensen 28 August 2018 в 13:39
поделиться
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
4
ответ дан Seraj Ahmad 28 August 2018 в 13:39
поделиться

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

15
ответ дан sfjac 28 August 2018 в 13:39
поделиться

Еще одна не охваченная точка - это параметры для инициализации классов. Поскольку пункт назначения 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
ответ дан Trilarion 28 August 2018 в 13:39
поделиться

Общий

Предполагая, что все спускается с 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
9
ответ дан Zags 28 August 2018 в 13:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: