Как работает метод super в Python
методынаследованиеООПпаттернысупер
Что проверяют
Знаете ли вы, что super() обращается не к родительскому классу, а к следующему классу в MRO конкретного экземпляра, и что при множественном наследовании это разные вещи.
Как отвечать
super()возвращает прокси-объект, который ищет атрибут в MRO типа экземпляра, начиная со следующего элемента после класса, где написан вызов. Порядок строится линеаризацией C3 и виден в__mro__.- Поэтому в ромбовидной иерархии
super()внутриBможет привести вC, который родителемBне является. Это и есть кооперативное наследование: каждый класс зовётsuper(), и цепочка проходит по всем участникам ровно один раз.
class A:
def go(self): print("A")
class B(A):
def go(self): print("B"); super().go()
class C(A):
def go(self): print("C"); super().go()
class D(B, C): pass
D().go() # B, C, A: super() внутри B ведёт в C
print(D.__mro__) # D, B, C, A, object- Форма без аргументов работает только внутри тела класса: компилятор кладёт скрытую ячейку
__class__. В функции, присвоенной классу после его создания, нужна полная формаsuper(D, self). - В
classmethodвызов связывается сcls, вstaticmethodбез аргументов не работает. - Кооперативная схема работает только при совместимых сигнатурах: обычно принимают и пробрасывают
**kwargs, помня, чтоobject.__init__лишних аргументов не принимает. - Польза есть и при одиночном наследовании: имя базового класса не дублируется, а вставленный в иерархию миксин работает без правок вызовов.
Где спотыкаются
Говорят «super это родительский класс». При множественном наследовании следующим может оказаться сосед по ромбу, и порядок зависит от класса экземпляра, а не от места вызова.
Смешивают super().__init__() и явный Base.__init__(self). Второй вариант в ромбе зовёт общего предка дважды и рвёт кооперативную цепочку.
Забывают вызвать super().__init__() в подклассе и получают недоинициализированный объект, особенно болезненно с миксинами.