Вызов родительского класса init с множественным наследованием, каков правильный путь?
скажем, у меня есть сценарий множественного наследования:
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
из сторонней библиотеки, что тогда? Есть ли подход, который может гарантировать, что все конструкторы родительского класса будут вызваны (и в правильном порядке, и только один раз)?
Edit: чтобы увидеть, что я имею в виду, если я делаю:
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 никогда не вызывается. Поэтому кажется, что если я не знаю / не контролирую инициалы классов, которые я наследую от (A
и B
) Я не могу сделать безопасный выбор для класса я пишу (C
).
5 ответов:
оба способа работают нормально. Подход с использованием
super()
ведет к большей гибкости для подклассов.в подходе прямого вызова,
C.__init__
можете называть какA.__init__
иB.__init__
.при использовании
super()
, классы должны быть разработаны для совместного множественного наследования, гдеC
звонкиsuper
, который ссылаетсяA
's код, который также вызоветsuper
вызываетB
' s код. Видеть http://rhettinger.wordpress.com/2011/05/26/super-considered-super подробнее о том, что можно сделать сsuper
.[ответ на вопрос как в дальнейшем редактировать]
поэтому кажется, что если я не знаю / не контролирую инициалы классов I наследовать от (A и B) я не могу сделать безопасный выбор для класса я письменность (с).
в указанной статье показано, как справиться с этой ситуацией, добавив класс-оболочку вокруг
A
иB
. В разделе, озаглавленном "как включить некооперативный класс", есть проработанный пример.можно было бы пожелать, чтобы множественное наследование было проще, позволяя вам легко составлять классы автомобилей и самолетов, чтобы получить FlyingCar, но реальность такова, что отдельно разработанные компоненты часто нуждаются в адаптерах или обертках, прежде чем соединяться так легко, как мы хотели бы :-)
еще одна мысль: если вы недовольны сочинять функциональность используя множественное наследование, вы можете использовать композицию для полного контроля над тем, какие методы вызываются в каких случаях.
ответ на ваш вопрос зависит от одного очень важного аспекта: ваши базовые классы предназначены для множественного наследования?
есть 3 различных сценария:
базовые классы являются несвязанными, автономными классами.
если ваши базовые классы являются отдельными сущностями, которые способны функционировать самостоятельно и они не знают друг друга, они не предназначен для нескольких наследование.
в этом случае вам придется вызывать каждый родительский конструктор вручную. Это проще без
super
.пример:
class Foo: def __init__(self): self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar class FooBar(Foo, Bar): def __init__(self, bar='bar'): Foo.__init__(self) # explicit calls without super Bar.__init__(self, bar) # To get the same results with `super`, you'd have to do this: # super().__init__() # super(Foo, self).__init__(bar) # Which is obviously much less intuitive.
важно: обратите внимание, что ни один
Foo
, ниBar
звонкиsuper().__init__()
! Вот почему ваш код не работает правильно. Из-за того, как алмазное наследование работает в python:
- классы, базовый класс которых
object
не следует называтьsuper().__init__()
. Как вы заметили, что это нарушит множественное наследование, потому что вы в конечном итоге вызываете другой класс__init__
, а неobject.__init__()
.- это также означает, что вы должны никогда не пишу класс, который наследует от
object
и нет__init__
метод. Не определив__init__
метод Вообще имеет тот же эффект, что и вызовsuper().__init__()
. Если ваш класс наследует непосредственно отobject
, обязательно добавьте пустой конструктор, например Итак:.
class Base(object): def __init__(self): pass
один из классов является миксина.
A mixin - это класс, который предназначен для использования с множественным наследованием. Это означает, что нам не нужно вызывать оба родительских конструктора вручную, потому что mixin автоматически вызовет 2-й конструктор для нас. Поскольку на этот раз нам нужно вызвать только один конструктор, мы можем сделать это с помощью
super
чтобы избежать нужно жестко закодировать имя родительского класса.пример:
class FooMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar class FooBar(FooMixin, Bar): def __init__(self, bar='bar'): super().__init__(bar) # a single call is enough to invoke # all parent constructors # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't # recommended because we don't want to hard-code the parent class.
важные детали здесь:
- миксин звонит
super().__init__()
и проходит через любые аргументы, которые он получает.- подкласс наследует от mixin первый:
class FooBar(FooMixin, Bar)
. Если порядок базовых классов неверен, конструктор mixin никогда не будет вызван.все базовые классы предназначены для кооперативное наследство.
классы, предназначенные для совместного наследования, очень похожи на миксины: они проходят через все неиспользуемые аргументы в следующий класс. Как и раньше, мы просто должны позвонить
super().__init__()
и все родительские конструкторы будут вызываться цепочкой.пример:
class CoopFoo: def __init__(self, **kwargs): super().__init__(**kwargs) self.foo = 'foo' class CoopBar: def __init__(self, bar, **kwargs): super().__init__(**kwargs) self.bar = bar class CoopFooBar(CoopFoo, CoopBar): def __init__(self, bar='bar'): super().__init__(bar=bar) # pass all arguments on as keyword # arguments to avoid problems with # positional arguments and the order # of the parent classes
в этом случае, порядок родительских классов не имеет значения. Мы могли бы также унаследовать от
CoopBar
во-первых, и код будет работать так же. Но это только потому, что все аргументы передаются как аргументы ключевого слова. Использование позиционных аргументов позволит легко получить неправильный порядок аргументов, поэтому для кооперативных классов принято принимать только аргументы ключевых слов.это также исключение из правила, о котором я упоминал ранее: оба
CoopFoo
иCoopBar
наследовать отobject
, но они все равно звонятsuper().__init__()
. Если бы они этого не сделали, то не было бы никакого совместного наследования.итог: правильный реализация зависит от классов, которые вы наследуете.
конструктор является частью открытого интерфейса класса. Если класс разработан как mixin или для совместного наследования, это должно быть задокументировано. Если документы не упоминают ничего подобного, можно предположить, что класс не предназначен для множественного наследования, и вы должны вызвать конструктор каждого родительского класса явно (без
super
).
эта статья помогает объяснить кооперативное множественное наследование:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
он упоминает полезный метод
mro()
Это показывает вам порядок разрешения метода. В вашем 2-м примере, где вы называетеsuper
inA
наsuper
вызов продолжается в MRO. Следующий класс в порядкеB
, поэтомуB
' s init называется в первый раз.вот более технический статья с официального сайта Python:
если вы умножаете классы подклассов из сторонних библиотек, то нет, нет слепого подхода к вызову базового класса
__init__
методы (или любые другие методы), которые фактически работают независимо от того, как запрограммированы базовые классы.
super
делает возможно для записи классов, предназначенных для совместной реализации методов как части сложных деревьев множественного наследования, которые не должны быть известны автору класса. Но нет никакого способа, чтобы использовать его правильно наследовать от произвольных классов, которые могут или не могут использоватьsuper
.по существу, предназначен ли класс для подкласса с использованием
super
или с прямыми вызовами к базовому классу-это свойство, которое является частью класса "public interface", и оно должно быть задокументировано как таковое. Если вы используете сторонние библиотеки так, как ожидал автор библиотеки, и библиотека имеет разумную документацию, она обычно сообщает вам, что вы должны сделать для подкласса конкретных вещей. Если нет, то вам нужно будет посмотреть исходный код для классов, которые вы подклассируете, и посмотреть, каково их соглашение о вызове базового класса. Если вы объединяете несколько классов из одной или нескольких сторонних библиотек таким образом, что авторы библиотеки не ожидайте, тогда может быть невозможно последовательно вызывать методы суперкласса на всех; если класс A является частью иерархии с использованиемsuper
и класс B является частью иерархии, которая не используйте super, тогда ни один из вариантов не будет гарантированно работать. Вам придется придумать стратегию, которая будет работать для каждого конкретного случая.
как сказал Раймонд в своем ответе, прямой вызов
A.__init__
иB.__init__
работает нормально, и ваш код будет читаемым.однако он не использует связь наследования между
C
и эти классы. Использование этой ссылки дает вам больше согласованности и делает возможные рефакторинги более легкими и менее подверженными ошибкам. Пример того, как это сделать:class C(A, B): def __init__(self): print("entering c") for base_class in C.__bases__: # (A, B) base_class.__init__(self) print("leaving c")