Как Python super () работает с множественным наследованием?

889

Я довольно новичок в объектно-ориентированном программировании на 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 ).

Callisto
источник
Фактически, множественное наследование является единственным случаем, где super()есть какое-либо применение. Я не рекомендовал бы использовать это с классами, использующими линейное наследование, где это просто бесполезные накладные расходы.
Бахсау
9
@Bachsau технически корректен в том смысле, что это небольшие накладные расходы, но super () более питоничен и допускает повторный факторинг и изменения в коде с течением времени. Используйте super (), если вам действительно не нужен именованный класс-специфичный метод.
Пол Уипп
2
Другая проблема super()заключается в том, что он заставляет каждый подкласс использовать его, а когда он не используется super(), каждый подкласс может сам решать. Если разработчик, использующий его, не знает super()или не знает, что он использовался, могут возникнуть проблемы с mro, которые очень трудно отследить.
Бахсау
Я нашел практически каждый ответ здесь запутывающим так или иначе. Вы бы на самом деле ссылались здесь вместо этого.
Матансер

Ответы:

708

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

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

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

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
КПБ
источник
12
Это становится более интересным (и, возможно, более запутанным), когда вы начинаете вызывать super () в First, Second и Third [ pastebin.com/ezTyZ5Wa ].
gatoatigrado
52
Я думаю, что отсутствие супер звонков в первых классах - действительно большая проблема с этим ответом; без обсуждения того, как / почему это важное критическое понимание вопроса теряется.
Сэм Хартман
3
Этот ответ просто неверен. Без супер () звонков у родителей ничего не произойдет. Ответ @ lifeless правильный.
Серин
8
@Cerin Цель этого примера - показать, как строится MRO. Пример НЕ предназначен для печати "first \ nsecond \ third" или чего-либо еще. И MRO действительно правильное: Четвертое .__ mro__ == (<класс ' main .Fourth'>, <класс ' main .Second'>, <класс ' main .Third'>, <класс ' main .First'>, < введите 'object'>)
rbp
2
Насколько я вижу, в этом ответе отсутствует один из вопросов ОП, а именно: «А что, если вы хотите запустить другой?». Я хотел бы увидеть ответ на этот вопрос. Мы просто должны явно назвать базовый класс?
Луч
252

Ваш код и другие ответы все глючат. Они пропускают 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
безжизненный
источник
90
Что делать, если эти классы нуждаются в разных параметрах для инициализации?
Calfzhou
2
"Кооперативное субклассирование"
Квант Метрополис
6
Таким образом, будут выполняться методы init базовых классов ОБА, в то время как исходный пример вызывает только первый init, встречающийся в MRO. Я предполагаю, что это подразумевается под термином «кооперативное разделение на подклассы», но разъяснение было бы полезно («Явное лучше, чем неявное», вы знаете;))
Quant Metropolis
1
Да, если вы передаете разные параметры методу, вызываемому через super, все реализации этого метода, идущие вверх по MRO к object (), должны иметь совместимые сигнатуры. Это может быть достигнуто с помощью ключевых параметров: принимайте больше параметров, чем использует метод, и игнорируйте дополнительные. Это обычно считается уродливым, и в большинстве случаев лучше добавлять новые методы, но init (почти?) Уникален как имя специального метода, но с определенными пользователем параметрами.
безжизненное
15
Дизайн множественного наследования действительно плох в Python. Базовым классам почти нужно знать, кто собирается его получить, и сколько других базовых классов будет выводить производный, и в каком порядке ... в противном случае superон либо не будет работать (из-за несоответствия параметров), либо не вызовет несколько баз (потому что вы не написали ни superв одной из баз, которая разрывает ссылку)!
Наваз
186

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

Visionscaper
источник
6
Я до сих пор не понимаю, почему: Внутри init из First super (First, self) .__ init __ () вызывает init из Second, потому что это то, что диктует MRO!
user389955
@ user389955 Созданный объект имеет тип Third, который имеет все методы init. Поэтому, если вы предполагаете, что MRO создает список всех функций инициализации в определенном порядке, при каждом супер вызове вы продвигаетесь на один шаг вперед, пока не достигнете конца.
Sreekumar R
15
Я думаю, что шаг 3 нуждается в дополнительном объяснении: если Thirdне наследовать от Second, то super(First, self).__init__вызовет object.__init__и после возврата будет напечатано «first». Но поскольку Thirdнаследует от обоих Firstи Secondвместо вызова object.__init__после First.__init__MRO диктует, что object.__init__сохраняется только последний вызов , а операторы print в FirstиSecond не достигаются до object.__init__возврата. Поскольку Secondбыл последний звонок object.__init__, он возвращается внутрь, Secondпрежде чем вернуться внутрь First.
MountainDrew
1
Интересно, что PyCharm, кажется, знает все это (его подсказки говорят о том, какие параметры идут с какими вызовами к super. Он также имеет некоторое представление о ковариантности входных данных, поэтому он распознает List[subclass]как подкласс List[superclass]if ( происходит из модуля PEP 483). iirc).subclasssuperclassListtyping
Reb.Cabin
Хороший пост, но мне не хватает информации относительно аргументов конструкторов, то есть что произойдет, если Второй и Первый ожидают разных аргументов? Конструктор First должен будет обработать некоторые аргументы и передать остальные второму. Это правильно? Мне не кажется правильным, что Первый должен знать о необходимых аргументах для Второго.
Кристиан К.
58

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

monoceres
источник
Это не проблема бриллиантов. Алмазная проблема включает в себя четыре класса, а вопрос ОП - только три.
Ян Гудфеллоу
147
objectчетвертый
GP89
28

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

Вот Aэто расширяемый базовый класс и Bи Cклассы подмешать оба , которые обеспечивают функцию 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)
brent.payne
источник
Я думаю, что, возможно, это должен быть отдельный вопрос-ответ, так как MRO является достаточно большой темой, без необходимости разбираться с различными аргументами в функциях с наследованием (множественное наследование является частным случаем этого).
безжизненное
8
Теоретически да. Практически, этот сценарий возникал каждый раз, когда я сталкивался с наследованием Diamond в python, поэтому я добавил его здесь. С тех пор, это то место, куда я хожу каждый раз, когда не могу точно избежать наследования алмазов. Вот некоторые дополнительные ссылки для будущего меня: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne
Нам нужны программы с семантически значимыми именами параметров. Но в этом примере почти все параметры имеют анонимные имена, что значительно усложнит первоначальному программисту документирование кода, а другому программисту - чтение кода.
Артур
Будем
@ brent.payne Я думаю, @Arthur подразумевал, что весь ваш подход основан на использовании args/, kwargsа не именованных параметров.
более
25

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

Seaux
источник
5
Он не будет вызван, потому что вы не вызывали каждый унаследованный класс. Проблема скорее в том, что если Firstи Secondоба наследуют другой класс и вызывают его напрямую, то этот общий класс (начальная точка ромба) вызывается дважды. Супер избегает этого.
Триларион,
@ Триларион Да, я был уверен, что это не так. Тем не менее, я не знал точно, и я не хотел утверждать, как будто я сделал, хотя это было очень маловероятно. Это хорошая точка зрения о том, objectчто тебя дважды называют. Я не думал об этом. Я просто хотел подчеркнуть, что вы вызываете родительские классы напрямую.
Seaux
К сожалению, это не работает, если init пытается получить доступ к каким-либо закрытым методам :(
Эрик Аронесты,
22

В целом

Предполагая, что все происходит от 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
Zags
источник
Я вижу, что вы можете получить доступ Leftс помощью super()из Child. Предположим, я хочу получить доступ Rightизнутри Child. Есть ли способ получить доступ Rightс Childпомощью супер? Или я должен прямо позвонить Rightизнутри super?
alpha_989
4
@ alpha_989 Если вы хотите получить доступ только к методу определенного класса, вам следует ссылаться на этот класс напрямую, а не на супер. Супер - это следовать цепочке наследования, а не переходить к методу определенного класса.
Загс
1
Спасибо за явное упоминание «Класс появляется только один раз в MRO». Это решило мою проблему. Теперь я наконец понимаю, как работает множественное наследование. Кто-то должен был упомянуть свойства MRO!
Тушар Вазирани
18

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

Марко Сулла
источник
Вау, это действительно ужасно. Жаль, что вы не можете просто сказать, какой именно суперкласс вы хотите назвать. Тем не менее, это дает мне еще больше стимула использовать композицию и избегать множественного наследования, такого как чума.
Том Басби
15

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

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

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

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

first 10
that's it
Серадж Ахмад
источник
1
это потому, что в классе First вы сначала назвали print, а затем super.
скалистая ци
2
это должно было иллюстрировать порядок вызова
Серадж Ахмад
4

В 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 будет выполнять мое кодирование, и убедиться, что наследование может получить доступ к методу, который я намеревался.

Встретимся
источник
Спасибо за подробную демонстрацию!
Тушар Вазирани
3

Я хотел бы добавить к тому, что @Visionscaper говорит вверху:

Third --> First --> object --> Second --> object

В этом случае интерпретатор не отфильтровывает объектный класс, потому что он дублирован, а скорее потому, что Second появляется в позиции головы и не появляется в хвостовой позиции в подмножестве иерархии. Пока объект появляется только в хвостовых позициях и не считается сильной позицией в алгоритме C3 для определения приоритета.

Линеаризация (MRO) класса C, L (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"
SUPI
источник
«скорее, потому что Second появляется в положении головы и не появляется в положении хвоста в подмножестве иерархии». Непонятно, что такое положение головы или хвоста, а также подмножество иерархии или подмножество, на которое вы ссылаетесь.
OrangeSherbet
Хвостовая позиция относится к классам, которые выше в иерархии классов и наоборот. Базовый класс «объект» находится в конце хвоста. Ключом к пониманию алгоритма mro является то, как «Second» выглядит как «Super». Обычно мы предполагаем, что это класс объекта. Это правда, но только с точки зрения первого класса. Однако, если смотреть с точки зрения класса «Третий», порядок иерархии для «Первый» отличается и рассчитывается, как показано выше. Алгоритм mro пытается создать эту перспективу (или подмножество иерархии) для всех нескольких унаследованных классов
supi
3

В 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 ровно ОДНО время для каждой унаследованной цепочки в том же порядке, в котором она была унаследована. Вы можете получить этот заказ, позвонив . мро :

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

rfedorov
источник
2

Возможно, есть еще что-то, что можно добавить, небольшой пример с 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остались бы без декорации.

mariotomo
источник
1

Размещение этого ответа для моей будущей референции.

Множественное наследование Python должно использовать алмазную модель, и сигнатура функции не должна изменяться в модели.

    A
   / \
  B   C
   \ /
    D

Пример кода будет:

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Здесь класс А есть object

Ахил Надх ПК
источник
1
Aдолжны также быть вызовом __init__. Aне «изобрел» метод __init__, поэтому он не может предположить, что какой-то другой класс мог иметь Aранее в своем MRO. Единственный класс, чей __init__метод не вызывает (и не должен) вызывать super().__init__это object.
Чепнер
Да. Вот почему я написал «А, objectможет быть, я думаю, я должен написать class A (object) : вместо этого
Akhil Nadh PC
Aне может быть, objectесли вы добавляете параметр к его __init__.
chepner