Скажем, у меня есть сценарий множественного наследования:
class A(object):
# code for A here
class B(object):
# code for B here
class C(A, B):
def __init__(self):
# What's the right code to write here to ensure
# A.__init__ and B.__init__ get called?
Там две типичные подходы к письменной форме C
«s __init__
:
- (Старый стиль)
ParentClass.__init__(self)
- (Новый стиль)
super(DerivedClass, self).__init__()
Однако, в любом случае, если родительские классы ( A
и B
) не следуют одному и тому же соглашению, тогда код не будет работать правильно (некоторые могут быть пропущены или вызваны несколько раз).
Так что опять правильно? Легко сказать «просто будьте последовательны, следуйте одному или другому», но если A
или B
из сторонней библиотеки, что тогда? Есть ли подход, который может обеспечить вызов всех конструкторов родительских классов (и в правильном порядке, и только один раз)?
Изменить: чтобы увидеть, что я имею в виду, если я делаю:
class A(object):
def __init__(self):
print("Entering A")
super(A, self).__init__()
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
A.__init__(self)
B.__init__(self)
print("Leaving C")
Тогда я получаю:
Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C
Обратите внимание, что B
init вызывается дважды. Если я сделаю:
class A(object):
def __init__(self):
print("Entering A")
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
super(C, self).__init__()
print("Leaving C")
Тогда я получаю:
Entering C
Entering A
Leaving A
Leaving C
Обратите внимание, что B
init никогда не вызывается. Таким образом, кажется, что если я не знаю / не контролирую init классов, которые я наследую ( A
и B
), я не могу сделать безопасный выбор для класса, который я пишу ( C
).
источник
Ответы:
Оба способа работают нормально. Использование подхода
super()
приводит к большей гибкости для подклассов.В режиме прямого звонка
C.__init__
можно звонить какA.__init__
иB.__init__
.При использовании
super()
классы должны быть разработаны для совместного множественного наследования приC
вызовахsuper
, что вызываетA
код, который такжеsuper
вызывает вызов, который вызываетB
код. См. Http://rhettinger.wordpress.com/2011/05/26/super-considered-super для более подробной информации о том, что можно сделатьsuper
.[Ответ на вопрос, как позже отредактировано]
В указанной статье показано, как справиться с этой ситуацией, добавив класс-обертку вокруг
A
иB
. В разделе, озаглавленном «Как включить некооперативный класс», есть разработанный пример.Можно было бы пожелать, чтобы множественное наследование было проще, позволяя вам без труда создавать классы Car и Airplane для получения FlyingCar, но реальность такова, что для компонентов, разработанных по отдельности, часто требуются адаптеры или оболочки, прежде чем они будут соединяться так легко, как хотелось бы :-)
Еще одна мысль: если вы недовольны компоновкой функциональности с использованием множественного наследования, вы можете использовать композицию для полного контроля над тем, какие методы вызывались и в каких случаях.
источник
super().__init__()
подход. Если я звонюA.__init__()
иB.__init__()
напрямую, то (если A и B звонятsuper
), я получаю инициат B, вызываемый несколько раз.Ответ на ваш вопрос зависит от одного очень важного аспекта: предназначены ли базовые классы для множественного наследования?
Есть 3 разных сценария:
Базовые классы - это не связанные, автономные классы.
Если ваши базовые классы являются отдельными объектами, которые способны функционировать независимо и не знают друг друга, они не предназначены для множественного наследования. Пример:
Важно: Обратите внимание , что ни один,
Foo
ниBar
звонковsuper().__init__()
! Вот почему ваш код не работал правильно. Из-за того, как наследование алмазов работает в python, классы, базовый класс которыхobject
не должен вызыватьсяsuper().__init__()
. Как вы заметили, это нарушит множественное наследование, потому что в итоге вы вызываете другой класс,__init__
а неobject.__init__()
. ( Отказ от ответственности: Как избежатьsuper().__init__()
вobject
-subclasses моя личная рекомендация и отнюдь не согласованного консенсуса в питона сообщества Некоторые люди предпочитают использовать.super
В каждом классе, утверждая , что вы всегда можете написать адаптер , если класс не ведет себя как вы ожидаете.)Это также означает, что вы никогда не должны писать класс, который наследуется
object
и не имеет__init__
метода. Отсутствие определения__init__
метода имеет тот же эффект, что и вызовsuper().__init__()
. Если ваш класс наследуется напрямуюobject
, добавьте пустой конструктор, например, так:В любом случае, в этой ситуации вам придется вызывать каждый родительский конструктор вручную. Есть два способа сделать это:
Без
super
С участием
super
Каждый из этих двух методов имеет свои преимущества и недостатки. Если вы используете
super
, ваш класс будет поддерживать внедрение зависимостей . С другой стороны, легче делать ошибки. Например, если вы измените порядокFoo
иBar
(какclass FooBar(Bar, Foo)
), вам придется обновитьsuper
вызовы, чтобы соответствовать. Без этогоsuper
вам не придется беспокоиться об этом, и код будет гораздо более читабельным.Один из классов - миксин.
Mixin класс , который предназначен для использования множественного наследования. Это означает, что нам не нужно вызывать оба родительских конструктора вручную, потому что миксин автоматически вызовет 2-й конструктор для нас. Поскольку на этот раз нам нужно вызывать только один конструктор, мы можем сделать это с тем,
super
чтобы избежать жесткого кодирования имени родительского класса.Пример:
Важные детали здесь:
super().__init__()
и передает любые аргументы, которые он получает.class FooBar(FooMixin, Bar)
. Если порядок базовых классов неправильный, конструктор миксина никогда не будет вызван.Все базовые классы предназначены для кооперативного наследования.
Классы, разработанные для кооперативного наследования, во многом похожи на миксины: они передают все неиспользованные аргументы следующему классу. Как и раньше, мы просто должны вызывать,
super().__init__()
и все родительские конструкторы будут вызываться по цепочке.Пример:
В этом случае порядок родительских классов не имеет значения. Мы могли бы также наследовать от
CoopBar
первого, и код все равно работал бы так же. Но это только так, потому что все аргументы передаются как аргументы ключевых слов. Использование позиционных аргументов позволит легко получить неправильный порядок аргументов, поэтому для кооперативных классов принято принимать только ключевые аргументы.Это также исключение из правила, о котором я упоминал ранее:
CoopFoo
и то и другоеCoopBar
наследуетсяobject
, но они все еще вызываютsuper().__init__()
. Если бы они этого не сделали, кооперативного наследства не было бы.Итог: правильная реализация зависит от классов, от которых вы наследуете.
Конструктор является частью открытого интерфейса класса. Если класс разработан как миксин или для совместного наследования, это должно быть задокументировано. Если в документах не упоминается ничего подобного, можно с уверенностью предположить, что класс не предназначен для совместного множественного наследования.
источник
super().__init__(*args, **kwargs)
в миксин и написать его первым. Это имеет столько смысла.Любой подход («новый стиль» или «старый стиль») будет работать, если у вас есть контроль над исходным кодом для
A
иB
. В противном случае может потребоваться использование класса адаптера.Доступный исходный код: правильное использование «нового стиля»
Здесь порядок разрешения методов (MRO) диктует следующее:
C(A, B)
A
сначала диктует , потомB
. MRO естьC -> A -> B -> object
.super(A, self).__init__()
продолжается вдоль цепи MRO, начатой вC.__init__
кB.__init__
.super(B, self).__init__()
продолжается вдоль цепи MRO, начатой вC.__init__
кobject.__init__
.Можно сказать, что этот случай предназначен для множественного наследования .
Доступный исходный код: правильное использование «старого стиля»
Здесь MRO не имеет значения, поскольку
A.__init__
иB.__init__
называются явно.class C(B, A):
будет работать так же хорошо.Хотя этот случай не «разработан» для множественного наследования в новом стиле, как это было в предыдущем случае, множественное наследование все еще возможно.
Теперь, что, если
A
иB
из сторонней библиотеки - то есть, у вас нет контроля над исходным кодом дляA
иB
? Краткий ответ: Вы должны спроектировать класс адаптера, который реализует необходимыеsuper
вызовы, а затем использовать пустой класс для определения MRO (см . Статью Рэймонда Хеттингера, вsuper
частности, раздел «Как включить некооперативный класс»).Сторонние родители:
A
не реализуютsuper
;B
делаетКласс
Adapter
реализуетsuper
так, чтоC
может определять MRO, который вступает в игру, когдаsuper(Adapter, self).__init__()
выполняется.А что если все наоборот?
Сторонние родители:
A
инвентарьsuper
;B
неЗдесь тот же шаблон, за исключением того, что порядок выполнения включен
Adapter.__init__
;super
сначала позвони, потом явный звонок. Обратите внимание, что в каждом случае со сторонними родителями требуется уникальный класс адаптера.Хотя вы можете справиться со случаями, когда вы не управляете исходным кодом
A
иB
используя класс адаптера, это правда, что вы должны знать, как инициализируют родительские классыsuper
(если они вообще существуют), чтобы сделать это.источник
Как сказал Раймонд в своем ответе, прямой вызов
A.__init__
иB.__init__
прекрасно работает, и ваш код будет читабельным.Однако он не использует связь наследования между
C
этими классами. Использование этой ссылки дает вам большую согласованность и делает возможный рефакторинг проще и менее подвержен ошибкам. Пример того, как это сделать:источник
Эта статья помогает объяснить кооперативное множественное наследование:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
В нем упоминается полезный метод,
mro()
который показывает вам порядок разрешения метода. В Вашем 2 Например, если вы звонитеsuper
вA
, тоsuper
вызов продолжается в MRO. Следующий класс в порядкеB
, вот почемуB
init вызывается в первый раз.Вот более техническая статья с официального сайта Python:
http://www.python.org/download/releases/2.3/mro/
источник
Если вы умножаете подклассы классов из сторонних библиотек, то нет, нет слепого подхода к вызову
__init__
методов базового класса (или любых других методов), который фактически работает независимо от того, как запрограммированы базовые классы.super
делает возможным классам записи , предназначенных для совместной реализации методов , как часть сложных множественного наследования деревьев , которые не должны быть известны классу автора. Но нет способа использовать его для правильного наследования от произвольных классов, которые могут или не могут использоватьsuper
.По сути, то, предназначен ли класс для подкласса с использованием
super
или с прямыми вызовами базового класса, является свойством, которое является частью «открытого интерфейса» класса, и это должно быть задокументировано как таковое. Если вы используете сторонние библиотеки так, как этого ожидал автор библиотеки, и у библиотеки есть разумная документация, она обычно говорит вам, что вам нужно сделать, чтобы создать подкласс определенных вещей. Если нет, то вам придется взглянуть на исходный код классов, которые вы подклассифицируете, и посмотреть, каково их соглашение о вызовах базовых классов. Если вы комбинируя несколько классов из одной или нескольких сторонних библиотек таким образом , что библиотека авторы не ожидали, то он не может быть возможным , чтобы последовательно вызывать методы супер-класса на всех; если класс A является частью иерархии с использованием,super
а класс B является частью иерархии, в которой не используется super, то ни один из этих вариантов не гарантированно сработает. Вы должны выяснить стратегию, которая работает для каждого конкретного случая.источник