#python #python-3.x #oop #multiple-inheritance
#python #python-3.x #ооп #множественное наследование
Вопрос:
Предположим, у вас есть простая настройка множественного наследования с двумя базовыми классами A
и B
и одним дочерним классом C
, наследуемым от обоих.
class A:
def __init__(self):
print("Started A's constructor")
# -- Not calling: super().__init__() --
print("Ended A's constructor")
class B:
def __init__(self):
print("Started B's constructor")
super().__init__()
print("Ended B's constructor")
class C(A, B):
def __init__(self):
print("Started C's constructor")
super().__init__()
print("Ended C's constructor")
При вызове
c = C()
мы получаем вывод
Started C's constructor
Started A's constructor
Ended A's constructor
Ended C's constructor
Если я хочу вызвать оба конструктора базового класса для каждого C
объекта и не имею доступа к базовому классу для добавления super().__init__()
вызова, что следует изменить или добавить к C
? Есть ли способ сделать это так, чтобы он не прерывался, если A
действительно был вызван super().__init__()
?
(Редактировать: для ясности, добавление super().__init__()
в A
вызовет B
конструктор, поскольку он следующий в MRO; чтобы было понятнее: пример кода)
Что еще более важно, в общем случае, когда требуется вызвать каждый __init__
метод каждого предка ровно один раз, и не был уверен, вызывалась ли каждая __init__
функция super().__init__()
, как можно гарантировать, что каждый родительский класс был вызван один и только один раз?
Если это невозможно, не подорвет ли это фундаментальные объектно-ориентированные принципы? Просто в этом случае реализация A
не должна влиять на поведение B
.
Комментарии:
1. Вы не должны вызывать super в
B.__init__
. Вы просто вызовете__init__
ofobject
.2. Почему разработчики не выбрали для
__init__
ofobject
ничего не делать, если они не намеревались, чтобы люди когда-либо вызывали его? Есть ли у вас какие-либо ссылки или более глубокое понимание того, почему он никогда не должен вызываться и не будет вызываться ни в каких библиотеках python? Такой ответ может быть более подходящим в качестве ответа, чем комментарий.3. Чтобы использовать совместное множественное наследование с
super
, тогда каждый класс должен вызывать super . Поместитеsuper().__init__()
вызов вA.__init__
4. @PaulRooney нет, это не так, как это работает. Вам нужно вызвать
super
вB
иA
для прохождения всей цепочки MRO. super не означает вызвать непосредственный родительский класс . Это означает вызов следующего в MRO . Если ваши классы собираются использовать совместное множественное наследование, тоsuper
необходимо вызвать5. @PaulRooney: Когда вы создаете
B
, суперзвонок просто вызываетobject.__init__
. В ситуации множественного наследования, в чем суть этого вопроса, суперзвонок может вызывать другое,__init__
чемobject.__init__
.
Ответ №1:
A
не используется super
, поэтому его нельзя использовать для совместного наследования. Вместо этого определите оболочку для A
, в соответствии с тем, что в super()
считается super!в! (Предполагая, что вы не можете просто исправить A
в первую очередь.)
class A:
def __init__(self):
print("Started A's constructor")
print("Ended A's constructor")
# AWrapper also needs to override each method defined by A;
# those simply delegate their work to the intenral instance of A
# For example,
#
# def some_method(self, x, y):
# return self.a.some_method(x, y)
#
class AWrapper:
def __init__(self, **kwargs):
self.a = A()
super().__init__(**kwargs)
class B:
...
class C(AWrapper, B):
...
Комментарии:
1. @Aran-Fey: Это помещает A в MRO, и теперь вы возвращаетесь к тому, что A
__init__
не используетсяsuper
.2. @Aran-Fey: Как вы планируете исправить A
__init__
? Переопределение не заменяет его. Он все еще находится в MRO, и он все еще собирается разорватьsuper
цепочку.3. @Aran-Fey Это вообще не работает, потому что
super
необязательно возвращает прокси к первому статически определяемому базовому классу; это полностью зависит от MROself
, который может быть экземпляром класса, о котором вы еще даже не знаете.4. Проблема в том, что это явно игрушечный пример, и я не собираюсь одобрять решение, которое работает только для этого игрушечного примера. Что, если
A
иB
имеют общего предка иA
вызывают его__init__
явно. В конечном итоге этот метод может быть вызван дважды. Весь смыслsuper
заключается в том, чтобы гарантировать, что каждый метод вызывается один раз в том порядке, в котором они отображаются в MRO. Если вы собираетесь использоватьsuper
, делайте это правильно, и это означает, что не нужно жестко кодировать вызовы определенных методов базового класса.5. Но если
A
явно вызываетсяobject.__init__
, как вы можете избежатьobject.__init__
повторного вызова? То, что выAWrapper
использовали композицию вместо наследования, не означает, чтоobject.__init__
она будет вызвана только один раз. Он будет вызван один раз изA.__init__
и один раз изB.__init__
. Я не понимаю, как композиция имеет здесь какое-либо преимущество перед наследованием.
Ответ №2:
Вы можете выполнить итерацию по __bases__
атрибуту class object для вызова __init__
метода каждого из родительских классов:
class C(A, B):
def __init__(self):
print("Started C's constructor")
for base in self.__class__.__bases__:
base.__init__(self)
print("Ended C's constructor")
так что C()
выводит:
Started C's constructor
Started A's constructor
Ended A's constructor
Started B's constructor
Ended B's constructor
Ended C's constructor
Комментарии:
1. Я рассматривал это, но это не сработает, если
A
вызовыsuper().__init__()
конструктора asB
будут вызываться дважды. Я проясню вопрос, что это может быть так, хотя общий случай, на котором я хотел сосредоточиться, охватывает эту возможность.2. Вы имеете в виду, что
A
это родительский классB
? Если это так, вы должны определитьB
сclass B(A):
помощью.3. Вовсе нет. Если добавить вызов к
A
конструктору, он вызоветB
конструктор, поскольку он следующий в MRO. Именно в этом нелогичном поведении я пытаюсь разобраться здесь.4. @blhsing нет, вовсе нет. Это не так
super
работает.super
вызывает следующий элемент в mro , а не суперкласс. Самое простое решение — иметьA
callsuper().__init__
inA.__init__
, тогда все это будет работать так, как вы надеетесь.5. @blhsing Возможно, это проясняет ситуацию: repl.it/repls/MatureCaringDirectories