У меня вариант такой:
class A: def __init__(self): print("first") class B: def __init__(self): print("second") class C(A, B): def __init__(self): A.__init__() B.__init__()
Но тут с зависимостями проблема. Через super
было бы веселее. Что скажете, такое решение подходящее?
Например можно поступить так:
In [30]: class A: ...: def __init__(self): ...: print("first") ...: ...: class B: ...: def __init__(self): ...: print("second") ...: ...: class C(A, B): ...: def __init__(self): ...: super(C, self).__init__() ...: super(A, self).__init__() ...: In [31]: obj = C() first second
Чтобы понимать более детально что происходит, стоит почитать про «Ромбовидное наследование»
Вот достаточно понятное объяснение:
По сути, объект класса super
запоминает аргументы переданные ему в момент инициализации и при вызове любого метода (super().__init__(self)
в примере выше) проходит по списку линеаризации класса второго аргумента (self.__class__.__mro__
), пытаясь вызвать этот метод по очереди для всех классов, следующих за классом в первом аргументе (класс C), передавая в качестве параметра первый аргумент (self
). Т.е. для нашего случая:
self.__class__.__mro__ = [C, A, B] super(C, self).__init__() => A.__init__(self) super(A, self).__init__() => B.__init__(self)
Достаточно только один вызов super().__init__()
использовать, если классы написаны в кооперативном стиле, поддерживающим множественное наследование:
class A: def __init__(self, a, **kwargs): print("A", a) super().__init__(**kwargs) class B: def __init__(self, b, **kwargs): print("B", b) super().__init__(**kwargs) class C(A, B): pass C(a=1, b=2)
Вывод:
A 1 B 2
Вызваны оба родительских метода в A и B классах, даже не определяя специализированный C.__init__
метод.
Классы созданы, следуя трём правилам:
- метод, вызываемый
super()
существует (__init__
метод у всех классов есть, включаяobject
) - вызываемые методы совместимы (поэтому
**kwargs
используется, чтобы адаптировать разные аргументы) - каждое определение метода использует
super()
для вызова родительских методов.
Если поменять порядок наследования, то и порядок вызовов также изменится:
class D(B, A): pass D(a=2, b=1)
Вывод:
B 1 A 2
Порядок противоположный первому примеру.
Если нужно явно определить дочерний __init__
, то опять таки: одного super() вызова достаточно:
class E(A, B): def __init__(self, e, **kwargs): print("E", e) super().__init__(**kwargs) E(a=2, b=3, e=1)
Вывод:
E 1 A 2 B 3
Эта техника поддерживает случаи когда один и тот же класс несколько раз встречается в иерархии («ромб»). C3 алгоритм используется для линеаризации порядка вызовов родительских методов, чтобы найти MRO в Питоне:
class F(C, E): pass F(a=2, b=3, e=1)
Вывод:
E 1 A 2 B 3
Не смотря на то, что классы А
, B
дважды предками класса F
являются, соответствующие __init__
методы только по одному разу вызываются, благодаря линеаризации. Порядок вызовов родительских __init__
методов:
print(*[klass.__name__ for klass in F.mro()], sep=' -> ') # F -> C -> E -> A -> B -> object
[/apcode]