Reputation: 541
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
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
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
Reputation: 18438
Even though your example is not proper use of OOP, you could TECHNICALLY speaking take advantage of the fact that
__init__
methods can be called accessing them through the class (well... sort of like any method, really)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