Наследование
При нарушении подобной иерархии классов отдельные «частичные» классы, которые мы называем «миксинами», будут «видеть» только то, что объявлено непосредственно в них и в их базовых классах. В вашем примере при написании класса A он ничего не знает о классе B - вы, как автор, можете знать, что методы из класса B будут присутствовать, потому что методы из класса A будут вызываться только из класса C, что наследует оба.
Ваши инструменты программирования, включая IDE, этого не знают. (То, что вы должны знать лучше, чем ваши помощники по программированию, - это побочный путь). Это сработает, если запустить, но это плохой дизайн.
Если все методы должны присутствовать непосредственно в одном экземпляре вашего последнего класса, все они должны «присутствовать» в суперклассе для них всех - вы даже можете писать независимые подклассы в разных файлах , а затем один подкласс, который унаследует их все:
from abc import abstractmethod, ABC
class Base(ABC):
@abstractmethod
def method_A_1(self):
pass
@abstractmethod
def method_A_2(self):
pass
@abstractmethod
def method_B_1(self):
pass
class A(Base):
def __init__(self, *args, **kwargs):
# pop consumed named parameters from "kwargs"
...
super().__init__(*args, **kwargs)
# This call ensures all __init__ in bases are called
# because Python linearize the base classes on multiple inheritance
def method_A_1(self):
...
def method_A_2(self):
...
class B(Base):
def __init__(self, *args, **kwargs):
# pop consumed named parameters from "kwargs"
...
super().__init__(*args, **kwargs)
# This call ensures all __init__ in bases are called
# because Python linearize the base classes on multiple inheritance
def method_B_1(self):
...
...
class C(A, B):
pass
(«AB C» и «abstractmethod» - это немного сахара - они будут работать, но этот дизайн будет работать без любое из этого - думал, что их присутствие помогает любому, кто смотрит на ваш код, понять, что происходит, и вызовет более раннюю ошибку времени выполнения, если вы за ошибку создадите экземпляр одного из неполных базовых классов)
Составной
Это работает, но если ваши методы на самом деле предназначены для совершенно разных доменов, вместо множественного наследования вы должны попробовать использовать «составной шаблон проектирования» .
Нет необходимость множественного наследования, если оно не возникает естественным путем.
В этом случае вы создаете экземпляры объектов классов, управляющих различными доменами в __init__
класса оболочки, и передаете свой собственный экземпляр этому дочернему элементу, который сохранит ссылку на него (в атрибут self.parent, например). Скорее всего, ваша IDE по-прежнему не будет знать, о чем вы говорите, но у вас будет более разумный дизайн.
class Parent:
def __init__(self):
self.a_domain = A(self)
self.b_domain = B(self)
class A:
def __init__(self, parent):
self.parent = parent
# no need to call any "super...init", this is called
# as part of the initialization of the parent class
def method_A_1(self):
...
def method_A_2(self):
...
class B:
def __init__(self, parent):
self.parent = parent
def method_B_1(self):
# need result from 'A' domain:
a_value = self.parent.a_domain.method_A_1()
...
В этом примере используются основные c языковых функций, но если вы решите go для этого в сложном приложении, вы можете усложнить его - есть шаблоны интерфейса, которые могут позволить вам поменять местами классы, используемые для разных доменов, в специализированных подклассах и так далее. Но обычно вам может понадобиться приведенный выше шаблон.