Как работает 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"
должны Third
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")
на 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()
.
согласно MRO
__init__
третьего называется первым.далее, согласно MRO, внутри
__init__
методsuper(Third, self).__init__()
разрешает__init__
Метод первый, который вызывается.внутри
__init__
первогоsuper(First, self).__init__()
называет__init__
of во-вторых, потому что это то, что диктует MRO!внутри
__init__
второйsuper(Second, self).__init__()
вызовы the__init__
объекта, который ничего не значит. После этого "второй" печатается.после
super(First, self).__init__()
завершено, "первый" напечатанный.после
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/
таким образом, в обоих примерах, приведенных ниже, это:
- ребенок
- левый
- право
- родитель
когда вызывается метод, первым вхождением этого метода в 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"