admin管理员组

文章数量:1126106

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 /. "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.

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.

Share Improve this question edited 2 days ago Ilya asked Jan 9 at 1:18 IlyaIlya 5414 silver badges22 bronze badges 8
  • 1 This is almost certainly not a job for multiple inheritance. – user2357112 Commented Jan 9 at 1:21
  • What should I use? Composition (with Foo being an attribute rather than a base class)? – Ilya 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. – gnome Commented Jan 9 at 1:37
  • "the caller and callee need to have a matching argument signature" – cards Commented 2 days ago
  • Thanks for the suggestions. They may or may not be applicable. I added a note to my question explaining why. – Ilya Commented 2 days ago
 |  Show 3 more comments

3 Answers 3

Reset to default 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)

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

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.

本文标签: Python initialization with multiple inheritanceStack Overflow