0

I have the following class hierarchy. The goal is for the calling code to choose either a base Foo object or a Foobar object that also provides the additional Bar functionality.

class Foo:
    def __init__(self):
        self.foo = 'foo'
        
class Bar:
    def __init__(self, attr):
        self.attr = attr

    def bar(self):
        print(self.attr + 'bar')
        
class Foobar(Foo, Bar):
    def __init__(self):
        super().__init__(self.foo)

But when I try to run it:

>>> fb = Foobar()
AttributeError: 'Foobar' object has no attribute 'foo'

What's the right way to initialize Foobar? I've read a number of articles and SO posts about initialization with multiple inheritance, but none where one base constructor requires a property of the other base class.

EDIT:

My actual use case is derived from https://www.pythonguis.com/tutorials/pyside6-plotting-matplotlib/. "Bar" is actually FigureCanvasQTAgg and "Foobar" corresponds to MplCanvas. Foobar must derive from FigureCanvasQTAgg because the Foobar object will be passed to a bunch of PySide6 code that uses attributes I don't know about. I'm trying to break out the regular matplotlib code into another base class (Foo) to I can make an alternate front end that doesn't use PySide6, but there may be a different way to achieve this goal.

EDIT 2:

Looks like the whole approach may be flawed. It would certainly be less taxing for my little brain to create foo in a separate function before trying to instantiate either a Foo or a Foobar.

8
  • 1
    This is almost certainly not a job for multiple inheritance. Commented Jan 9 at 1:21
  • What should I use? Composition (with Foo being an attribute rather than a base class)? Commented Jan 9 at 1:29
  • 1
    You're trying to call self.foo where foo is from the parent class Foo before Foobar has been fully initialized — hence the error. If you need Foobar to exhibit this behaviour, why not use a singleton/factory model instead of inheritance? It seems like what you're after is composition instead of inheritance anyway. Commented Jan 9 at 1:37
  • "the caller and callee need to have a matching argument signature" Commented Jan 9 at 13:34
  • Thanks for the suggestions. They may or may not be applicable. I added a note to my question explaining why. Commented Jan 9 at 16:52

3 Answers 3

1

When using multiple inheritance, your classes (most of them, with maybe one exception in the while inheritance tree), have to act collaboratively. And that implies that any method on a class that knows it is not the "root base class" for that method, has to call the super version of itself - this way it ensure that when that method is called in a subclass which also have other parents, all the versions of the method are executed.

In your example, that implies that Bar.__init__ should be calling super().__init__(), just consuming the parameters it knows about - and then FooBar.__init__() will also call super().__init__:

TL;DR: this is the "one obvious way to do it" - and calling any superclass method explicitly using the class name is a hack which, although can be used to circunvent some classes that doesn't have collaborative inheritance mechanisms written, should be regarded as that: temporary hacks. The fix is to make your classes collaborate in the proper way, with super() calls.

class Foo:
    def __init__(self, **kwargs):
        self.foo = 'foo'
        super().__init__(**kwargs)
        # by also including the super() call here, you can then
        # inehrit from `Foo` and `Bar` in any other.

class Bar:
    def __init__(self, attr, **kwargs):
        self.attr = attr
        super().__init__(**kwargs)
        # when "Bar" is instantiaed by itself, this call 
        # will call `object.__init__()`, which does nothing
        # but when called from an instance with multiple inheritance
        # which includes `Foo` above Bar in the __mro__,
        # it will call `Foo.__init__`. 

    def bar(self):
        # other classes aroudn don't have "bar" methods -
        # so, no need to call `super().bar()` 
        print(self.attr + 'bar')

class Foobar(Foo, Bar):
    def __init__(self, attr):  # must receive all parameters that known superclasses will need!
        ...
        super().__init__(attr=attr)
Sign up to request clarification or add additional context in comments.

3 Comments

This creates a chicken-and-egg problem in that I need the attr of Bar to be an argument of Foobar.__init__ even though it's only created inside Foo.__init__. But I don't know anymore: maybe the chicken-and-egg problem is intrinsic to how I posed the question.
This doesn 't "create" the problem - this is innerent to how classes and inheritance work. If a "Foo" object needs "attr" to be created, and "FooBar" is a "Foo", "FooBar" needs "attr". What can be done, is that if "FooBar" knows what "attr" it needs from some source other than getting it as an argument, it could pass that to it's super().__init__() call instead.
so it's not actually a solution of the question
0

A demo for you(Just to tell why your Foobar does not have a attr attribution, don't don't do it in real code):

class Foo:
    def __init__(self):
        self.foo = 'foo'
        
class Bar:
    def __init__(self, attr):
        self.attr = attr

    def bar(self):
        print(self.attr + 'bar')
        
class Foobar(Foo, Bar):
    def __init__(self):
        super().__init__() # Will use: Foo.__init__
        Bar.__init__(self, self.foo)

Foobar().bar()
# foobar

I would rather do:

class Foo:
    foo = "foo"


class Bar:
    def __init__(self, attr) -> None:
        self.attr = attr

    def bar(self) -> None:
        print(self.attr + "bar")


class FooBar(Foo, Bar):
    def __init__(self) -> None:
        super().__init__(self.foo)


FooBar().bar()
# foobar

3 Comments

This seems to work. I need some more time to translate it back to my actual use case, but it answers the question as posed.
@Waket Zheng: highly recommend to remove the 'demo' part, because (a) calling an instance method by doing Class(self, args) isn't very readable. (b) calling __init__ explicitly in this manner will cause the method to be called twice - many real-world classes will be unprepared to handle this and can crash or cause other issues. IMPORTANT: as-coded, this has another critical issue: the resulting FooBar instance has no ".foo" attribute, because Foo.init will not be called, this will cause any Foo methods that expect it to fail.
@LeoK You are right! I updated my answer.
0

Even though your example is not proper use of OOP, you could TECHNICALLY speaking take advantage of the fact that

  1. __init__ methods can be called accessing them through the class (well... sort of like any method, really)
  2. Python is interpreted and run line by line

So with that in mind you could do:

class Foobar(Foo, Bar):
    def __init__(self):
        Foo.__init__(self)  # At this point, self has .foo
        Bar.__init__(self, self.foo)

However, by looking at the clarification you posted, I would strongly recommend looking into whether I need to make my FooBar class inherit from FigureCanvasQTAgg or I could just have FooBar have a FigureCanvasQTAgg attribute. I mean: trying to split what seems to be the graphical representation provided by Matplotlib from your logic in the class.

2 Comments

CPython first compiles the script before executing the resulting byte code. The fact that def statements are executable, rather than simply definitions, is what gives the "impression" of line-by-line execution.
@chepner The impression is all that should matter to the programmer, the rest is implementation details.

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.