Python - Дочерний класс для вызова функции из другого Дочернего класса - PullRequest
2 голосов
/ 26 мая 2020

У меня довольно большой класс, который я хочу разбить на более мелкие классы, каждый из которых обрабатывает отдельную часть целого. Таким образом, каждый ребенок заботится только об одном аспекте целого.

Example

Каждый из этих дочерних классов по-прежнему должен взаимодействовать друг с другом. Например, Data Access создает словарь, к которому должен иметь доступ контроллер печати. А затем при построении графика Контроллеру необходимо обновить данные на основном GUI контроллере. Но у этих детей есть множество других функций взаимодействия.

Как мне этого добиться?

Я читал Метаклассы , Совместное множественное наследование и Чудеса совместного множественного наследования , но я не могу понять, как это сделать.

Ближайший, что я нашел, это следующий код:

class A:
    def __init__(self):
        self.myself = 'ClassA'

    def method_ONE_from_class_A(self, caller):
        print(f"I am method ONE from {self.myself} called by {caller}")
        self.method_ONE_from_class_B(self.myself)

    def method_TWO_from_class_A(self, caller):
        print(f"I am method TWO from {self.myself} called by {caller}")
        self.method_TWO_from_class_B(self.myself)


class B:
    def __init__(self):
        self.me = 'ClassB'

    def method_ONE_from_class_B(self, caller):
        print(f"I am method ONE from {self.me} called by {caller}")
        self.method_TWO_from_class_A(self.me)

    def method_TWO_from_class_B(self, caller):
        print(f"I am method TWO from {self.me} called by {caller}")


class C(A, B):

    def __init__(self):
        A.__init__(self)
        B.__init__(self)

    def children_start_talking(self):
        self.method_ONE_from_class_A('Big Poppa')


poppa = C()
poppa.children_start_talking()

который приводит к правильному результату:

I am method ONE from ClassA called by Big Poppa
I am method ONE from ClassB called by ClassA
I am method TWO from ClassA called by ClassB
I am method TWO from ClassB called by ClassA

Но ... даже если Класс B и Класс A правильно вызывают другие дочерние функции, они фактически не находят своего объявления. Я также не «вижу» их, когда набираю код, что одновременно расстраивает и тревожит, что я делаю что-то не так.

As shown here

Cannot find declaration

Есть хороший способ добиться этого? Или это действительно плохая идея?

EDIT: Python 3.7, если это имеет значение.

1 Ответ

1 голос
/ 26 мая 2020

Наследование

При нарушении подобной иерархии классов отдельные «частичные» классы, которые мы называем «миксинами», будут «видеть» только то, что объявлено непосредственно в них и в их базовых классах. В вашем примере при написании класса 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 для этого в сложном приложении, вы можете усложнить его - есть шаблоны интерфейса, которые могут позволить вам поменять местами классы, используемые для разных доменов, в специализированных подклассах и так далее. Но обычно вам может понадобиться приведенный выше шаблон.

...