James Potter
James Potter

Reputation: 68

Class has no attribute of a variable from a parent class using multiple inheretance

This has probably been asked to death, but I really think this question will help someone because I really can't find a question or answer for this.

I have made an effort to boil the code down to its smallest structure (ignoring the dodgy naming)

Firstly here is the stack trace of the issue:

Traceback (most recent call last):
  File "C:\xx\xx\xx\main.py", line 30, in <module>
    DoSomething().method()
  File "C:\xx\xx\xx\main.py", line 27, in method
    self.some_class_method()
  File "C:\xx\xx\xx\main.py", line 12, in some_class_method
    print(self.variable)
AttributeError: 'DoSomething' object has no attribute 'variable'

Process finished with exit code 1

This is the failing code (From top to bottom: lowest level to highest level, to finally calling the highest level class(Highest being the most child)):

class ParentConfig:
    def __init__(self):
        pass


class SomeClass:
    def __init__(self):
        super().__init__()
        self.variable = 'value'

    def some_class_method(self):
        print(self.variable)


class Config(ParentConfig, SomeClass):
    def __init__(self):
        super().__init__()
        pass


class DoSomething(Config):
    def __init__(self):
        super().__init__()
        pass

    def method(self):
        self.some_class_method()


DoSomething().method()

I can get the code to work in two ways:

One, remove 'ParentConfig' parent class

class Config(<removed>, SomeClass):
    def __init__(self):
        super().__init__()
        pass

Two, call both __init__s separately

class Config(ParentConfig, SomeClass):
    def __init__(self):
        ParentConfig().__init__()
        SomeClass().__init__()
        pass

Now, to be clear the second 'solution' doesn't work in this example, but it does fix the issue in my program, apologies for not having a perfect example.

The main point is, the class 'DoSomething' can't use self.variable when calling the method using it.

Bonus points if someone can fix my example to work when calling the ParentConfig().__init__() and SomeClass().__init__() individually but not when using just super().__init__

I hope this is enough info. In the mean time I will work on a better example and edit this.

Edit:

TLDR on Karl Knechtels answer for newbies:

Either remove def __init__(self) from ParentClass

Or

add a super().__init__() to the def __init__(self) in ParentClass

Upvotes: 3

Views: 1556

Answers (1)

Karl Knechtel
Karl Knechtel

Reputation: 61635

class ParentConfig:
    def __init__(self):
        pass

The problem is already here. Anything that has ParentConfig as a base, directly or indirectly, will stop at this point when following the super() chain.

class Config(ParentConfig, SomeClass):

Config (as well as anything that has it as a base, directly or indirectly) will consider ParentConfig before SomeClass when super() is called in Config.__init__. SomeClass.__init__ therefore does not get called, and the .variable is not set.

Python's method for dealing with the "diamond inheritance" problem is that super() is cooperative. It does not route to an immediate base class of the current class. It routes to the next class in the method resolution order of the actual self object.

Here, it should be used in the ParentConfig:

class ParentConfig:
    def __init__(self):
        super().__init__()

(Or just omit __init__, as this is the default behaviour.)

When initializing a Config (or a DoSomething), this super() call will route, not to object, but to SomeClass, as desired. Why? Because of the MRO, which you can inspect at runtime:

>>> Config.__mro__
(<class '__main__.Config'>, <class '__main__.ParentConfig'>, <class '__main__.SomeClass'>, <class 'object'>)
>>> DoSomething.__mro__
(<class '__main__.DoSomething'>, <class '__main__.Config'>, <class '__main__.ParentConfig'>, <class '__main__.SomeClass'>, <class 'object'>)

For more details, see Python's super() considered super!, the authoritative essay on the topic written by a member of the Python dev team (and also a really good teacher in general).

Upvotes: 3

Related Questions