Какие проблемы могут возникнуть при множественном наследовании в Python?
Что проверяют
Понимаете ли вы, как Python выбирает метод при нескольких родителях и что на самом деле делает super(). Следом почти всегда просят выписать MRO для ромба или объяснить, почему миксин в Django ставят левее базового представления.
Как отвечать
Сначала снимите миф: неоднозначности, как в C++, в Python нет. Порядок поиска атрибутов задаёт MRO, построенный C3-линеаризацией, посмотреть его можно через D.__mro__ или D.mro(). Для ромба class D(B, C), где B и C наследуют A, порядок будет D, B, C, A, object. Проблемы возникают, когда этот порядок не учитывают:
super()ведёт не к родителю, а к следующему классу в MRO: внутриBпри работе с объектомDон вызоветC, о которомBничего не знает;- несовместимые сигнатуры: если
__init__у родителей принимают разные аргументы, кооперативная цепочка ломается. Обычный приём: принимать**kwargs, забирать своё и передавать остальное дальше; - разорванная цепочка: один класс не вызвал
super().__init__(), и инициализация всех классов правее в MRO молча не выполнилась; - двойной вызов: явный
A.__init__(self)вместоsuper()в ромбе вызываетAдважды; - невозможный порядок:
class X(A, B),class Y(B, A)и затемclass Z(X, Y)даютTypeError: Cannot create a consistent method resolution order; - конфликты раскладки и метаклассов: встроенные типы с несовместимой раскладкой в памяти или несвязанные метаклассы у баз дают
TypeErrorуже при объявлении класса.
Практичный вывод: множественное наследование в Python живёт в форме миксинов, узких классов с одним поведением и без своего состояния. Остальное чаще решается композицией или протоколами из typing.
Где спотыкаются
Говорят, что Python «не знает, какой метод вызвать». Знает, и детерминированно, по MRO.
Объясняют super() как «вызов родительского класса». На ромбе такое объяснение даёт неверный порядок вызовов.
Ставят миксин справа: class MyView(View, LoginRequiredMixin). Метод dispatch у View находится раньше, и проверка логина не выполняется вовсе.