Как работает 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 it"
То, что я не получаю: будет ли класс Third()
наследовать оба метода конструктора? Если да, то какой из них будет запущен с помощью super() и почему?
А что, если вы хотите запустить другой? Я знаю, что это имеет какое-то отношение к порядку разрешения метода Python (MRO).
Ответы
Ответ 1
Это подробно описано с достаточным количеством подробностей самим Гвидо в его посте " Постановление о разрешении метода" (включая две более ранние попытки).
В вашем примере 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 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'>)
Ответ 2
Ваш код и другие ответы не работают. Они пропускают вызовы 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__()
.
Это то, что я получаю:
>>> Third()
second
first
third
Ответ 3
Я хотел немного уточнить ответ безжизненным, потому что когда я начал читать о том, как использовать 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()
.
- Согласно MRO
Third.__init__
выполняет.
- печатает
Third(): entering
- затем выполняется
super(Third, self).__init__()
и MRO возвращает вызванный First.__init__
.
First.__init__
выполняется.
- печатает
First(): entering
- затем выполняется
super(First, self).__init__()
и MRO возвращает вызванный Second.__init__
.
Second.__init__
выполняется.
- печатает
Second(): entering
- затем выполняется
super(Second, self).__init__()
и MRO возвращает вызванный object.__init__
.
object.__init__
выполняется (в коде нет операторов печати)
- выполнение возвращается к
Second.__init__
, который затем печатает Second(): exiting
- выполнение возвращается к
First.__init__
, который затем печатает First(): exiting
- выполнение возвращается к
Third.__init__
, который затем печатает Third(): exiting
Здесь подробно объясняется, почему создание экземпляра Third() приводит к:
Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting
Алгоритм MRO был улучшен с Python 2.3 и выше, чтобы хорошо работать в сложных случаях, но я предполагаю, что использование "обхода слева направо в глубину" + "удаление дубликатов, ожидающих последнего" все еще работает в большинстве случаи (пожалуйста, прокомментируйте, если это не так). Обязательно прочитайте пост в блоге Гвидо!
Ответ 4
Это называется Diamond Problem, на странице есть запись на Python, но вкратце, Python вызовет методы суперкласса слева направо.
Ответ 5
Это касается того, как я решил выдать множественное наследование с разными переменными для инициализации и иметь несколько 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)
Ответ 6
Я понимаю, что это напрямую не отвечает на вопрос 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__()
не будет вызван.
Ответ 7
В общем и целом
Предполагая, что все сходит с object
(вы сами по себе, если это не так), Python вычисляет порядок разрешения метода (MRO) на основе вашего дерева наследования классов. MRO удовлетворяет 3 свойствам:
- Дети класса приходят перед родителями
- Левые родители приходят перед правильными родителями
- Класс появляется только один раз в MRO
Если такой порядок не существует, ошибки Python. Внутренняя работа - это C3-линеризация родословных классов. Подробнее об этом читайте здесь: 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
Ответ 8
О комментарий @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
, когда они переопределяют метод, в то время как это одно из немногих действительно полезных и разумных способов использования этих "магических переменных".
Ответ 9
Еще одна не охваченная точка - это параметры для инициализации классов. Поскольку пункт назначения 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
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 it"
t = Third()
Выход
first 10
second 20
that it
Call to Third() определяет init, определенный в третьем. И вызов super в этой процедуре вызывает init, определенный в First. MRO = [Первый, Второй].
Теперь вызов super в init, определенный в First, продолжит поиск MRO и найдет init, определенный во втором, и любой вызов супер ударит по объекту по умолчанию init. Я надеюсь, что этот пример прояснит концепцию.
Если вы не называете super от First. Цепь останавливается, и вы получите следующий результат.
first 10
that it
Ответ 11
В 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 будет выполнять мое кодирование, и убедиться, что наследование может получить доступ к методу, который я намеревался.
Ответ 12
Я хотел бы добавить, что говорит @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 it"
становится очевидным, как этот метод будет разрешен
Third.__init__() ---> First.__init__() ---> Second.__init__() --->
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that it"
Ответ 13
Может быть, есть еще кое-что, что можно добавить, небольшой пример с 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
бы остаться без отделки.
Ответ 14
В 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 ровно ОДНО время для каждой унаследованной цепочки в том же порядке, в котором она была унаследована. Вы можете получить этот заказ, позвонив .mro :
Четвертый → Третий → Первый → Второй → База → объект