2

I have a pretty big class that i want to break down in smaller classes that each handle a single part of the whole. So each child takes care of only one aspect of the whole.

Example "Design" before and after

Each of these child classes still need to communicate with one another. For example Data Access creates a dictionary that Plotting Controller needs to have access to. And then plotting Controller needs to update stuff on Main GUI Controller. But these children have various more inter-communication functions.

How do I achieve this?

I've read Metaclasses, Cooperative Multiple Inheritence and Wonders of Cooperative Multiple Inheritence, but i cannot figure out how to do this.

The closest I've come is the following code:

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()

which results correctly in:

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

But... even though Class B and Class A correctly call the other children's functions, they don't actually find their declaration. Nor do i "see" them when i'm typing the code, which is both frustrating and worrisome that i might be doing something wrong.

As shown here

Cannot find declaration

Is there a good way to achieve this? Or is it an actually bad idea?

EDIT: Python 3.7 if it makes any difference.

4
  • What do you mean by find their declaration? I don't understand what's wrong with your solution. Commented May 26, 2020 at 14:51
  • It means that in Class B when i write self. it does not show the functions from any other class except Class B. You can clearly see in the second picture that it shows "Unresolved attribute reference" for that function, which exists in class A. Here is a image showing that it cannot "see" the methods from the other Child classes. i.imgur.com/YkmFb92.png even thought it exists there, and it calls it correctly. Commented May 26, 2020 at 15:08
  • Why do you think self should show other class methods? Are you intending those classes to inherit, or should they contain instances of one another? Commented May 26, 2020 at 15:12
  • The first schema is about a high level modeling of cooperating objects. Fine. But what inheritance are you trying to put there? Remember, inheritance is a is a relation, and it looks like you are using the wrong tool here. Commented May 26, 2020 at 15:22

1 Answer 1

1

Inheritance

When breaking a class hierarchy like this, the individual "partial" classes, we call "mixins", will "see" only what is declared directly on them, and on their base-classes. In your example, when writing class A, it does not know anything about class B - you as the author, can know that methods from class B will be present, because methods from class A will only be called from class C, that inherits both.

Your programming tools, the IDE including, can't know that. (That you should know better than your programming aid, is a side track). It would work, if run, but this is a poor design.

If all methods are to be present directly on a single instance of your final class, all of them have to be "present" in a super-class for them all - you can even write independent subclasses in different files, and then a single subclass that will inherit all of them:

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

(The "ABC" and "abstractmethod" are a bit of sugar - they will work, but this design would work without any of that - thought their presence help whoever is looking at your code to figure out what is going on, and will raise an earlier runtime error if you per mistake create an instance of one of the incomplete base classes)

Composite

This works, but if your methods are actually for wildly different domains, instead of multiple inheritance, you should try using the "composite design pattern".

No need for multiple inheritance if it does not arise naturally.

In this case, you instantiate objects of the classes that drive the different domains on the __init__ of the shell class, and pass its own instance to those child, which will keep a reference to it (in a self.parent attribute, for example). Chances are your IDE still won't know what you are talking about, but you will have a saner design.


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()
        ...


This example uses the basic of the language features, but if you decide to go for it in a complex application, you can sophisticate it - there are interface patterns, that could allow you to swap the classes used for different domains, in specialized subclasses, and so on. But typically the pattern above is what you would need.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much for this very detailed response. The Inheritance part I understand. Basically what i'm missing is the Base Class which contains all the child methods that may be called by other children. The Composite part is probably what I had in mind but I didn't know how it's called. I did use parent at a point, but it seemed as a bad idea because calling other child functions needed parent.child_a.function() which to me looks like spaghetti code. I will probably try both of these tomorrow and see which one feels more sane, and would confuse anyone less in the future.
The parent.child_a.method_a is not bad - it is better than having hundreds of unrelated methods in the same namespace. The "adaptor" pattern can mitigate that - essentially, you'd create a function that fetches and calls self.parent.child_a.method_a() by doing adapt(self, "domain_a", method_a)()
But perceive that y using and adaptor, you may have to type less, may have semantically better looking code, but the chances that your IDE will know what you are talking about will go to zero.
Yea it seems that having both a clean solution and making it less verbose is pretty impossible. By the way i tried the inheritance part and it showed that abstractmethod was deprecated in Python 3.3 and it recommended using classmethod instead. Which also seems to work without ABC imports. Anyway thanks a lot again for the help. Will try tomorrow.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.