1

Python 3.6

I just found myself programming this type of inheritance structure (below). Where a sub class is calling methods and attributes of an object a parent has.

In my use case I'm placing code in class A that would otherwise be ugly in class B.

Almost like a reverse inheritance call or something, which doesn't seem like a good idea... (Pycharm doesn't seem to like it)

Can someone please explain what is best practice in this scenario?

Thanks!

class A(object):

    def call_class_c_method(self):
        self.class_c.do_something(self)

class B(A):

    def __init__(self, class_c):
        self.class_c = class_c
        self.begin_task()

    def begin_task(self):
        self.call_class_c_method()

class C(object):

    def do_something(self):
        print("I'm doing something super() useful")

a = A
c = C
b = B(c)

outputs:

I'm doing something super() useful
James Schinner
  • 1,549
  • 18
  • 28
  • Why you have class C when you can put do_something in class A directly ? – Kishan Mehta Jul 05 '17 at 10:53
  • 'In my use case I'm placing code in class A that would otherwise be ugly in class B' – James Schinner Jul 05 '17 at 10:53
  • Any reason why would do something like this? It's a very definition of an anti-pattern. – zwer Jul 05 '17 at 10:54
  • from class B you are calling class A method which is calling class C which more ugly. – Kishan Mehta Jul 05 '17 at 10:56
  • 1
    If you're not using the class `A` for anything (which you can't really do because it tries to reference attributes it doesn't have), there is no reason to have it. In fact, to me having the `call_class_c_method` method be in `A` and not `B` is orders of magnitude more ugly. – Kendas Jul 05 '17 at 10:56
  • To explain further. I'm trying to encapsulate an idea in `class A` that just happens to need to call a method in `class B` – James Schinner Jul 05 '17 at 10:57
  • If you don't want the call to class C in B, then "outsource" it to a normal function (maybe even in its own library module) but don't outsurce it to the parent class. The object of type class C could be a parameter to the function. – Mike Scotty Jul 05 '17 at 10:58
  • place where you are calling call_class_c_method whats wrong in calling do_something directly at that point ? – Kishan Mehta Jul 05 '17 at 10:59
  • In my use case. 'class A` is a set of methods to perform one task. If I call `class C` from `class B` then the **one** task `class A` was meant to do is now being done in **two** `class'` – James Schinner Jul 05 '17 at 11:05
  • Are you sure the classes are related (as in inheritance is needed) here? – Kendas Jul 05 '17 at 11:07
  • That's a good question. I could turn `class A` into a library as mpf8 suggested. And then call the library methods in `class B`. I'd need to think about how that would affect the flow of data. Hmm... good ideas! – James Schinner Jul 05 '17 at 11:14
  • 1
    Somewhat unrelated, you are passing around a bunch of class objects instead of *instances* of those classes. `a = A(); c = C(); b = B(c)` – chepner Jul 05 '17 at 11:35
  • Fair point, actually didn't consider that. – James Schinner Jul 05 '17 at 12:57

2 Answers2

2

There is nothing wrong with implementing a small feature in class A and use it as a base class for B. This pattern is known as mixin in Python. It makes a lot of sense if you want to re-use A or want to compose B from many such optional features.

But make sure your mixin is complete in itself!

The original implementation of class A depends on the derived class to set a member variable. This is a particularly ugly approach. Better define class_c as a member of A where it is used:

class A(object):

    def __init__(self, class_c):
        self.class_c = class_c

    def call_class_c_method(self):
        self.class_c.do_something()

class B(A):

    def __init__(self, class_c):
        super().__init__(class_c)
        self.begin_task()

    def begin_task(self):
        self.call_class_c_method()

class C(object):
    def do_something(self):
        print("I'm doing something super() useful")

c = C()
b = B(c)
MB-F
  • 22,770
  • 4
  • 61
  • 116
1

I find that reducing things to abstract letters in cases like this makes it harder for me to reason about whether the interaction makes sense. In effect, you're asking whether it is reasonable for a class(A) to depend on a member that conforms to a given interface (C). The answer is that there are cases where it clearly does. As an example, consider the model-view-controller pattern in web application design. You might well have something like

class Controller:

    def get(self, request)
        return self.view.render(self, request)

or similar. Then elsewhere you'd have some code that found the view and populated self.view in the controller. Typical examples of doing that include some routing lookups or include having a specific view associated with a controller. While not Python, the Rails web framework does a lot of this.

When we have specific examples, it's a lot easier to reason about whether the abstractions make sense.

In the above example, the controller interface depends on having access to some instance of the view interface to do its work. The controller instance encapsulates an instance that implements that view interface.

Here are some things to consider when evaluating such designs:

  • Can you clearly articulate the boundaries of each interface/class? That is, can you explain what the controller's job is and what the view's job is?

  • Does your decision to encapsulate an instance agree with those scopes?

  • Do the interface and class scopes seem reasonable when you think about future extensibility and about minimizing the scope of code changes?

Sam Hartman
  • 6,210
  • 3
  • 23
  • 40
  • Yes, you explained what I was trying to achieve in a way that makes a lot more sense. I'm going to accept kazemakase answer, as it explains how to implement such a thing. Though, this post has given me something to take away and understand in my own time. Thanks! – James Schinner Jul 05 '17 at 12:16