Ilya
Ilya

Reputation: 541

Python initialization with multiple inheritance

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.

Upvotes: 0

Views: 100

Answers (3)

Waket Zheng
Waket Zheng

Reputation: 6371

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

Upvotes: 0

jsbueno
jsbueno

Reputation: 110591

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)

Upvotes: 1

Savir
Savir

Reputation: 18438

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.

Upvotes: 0

Related Questions