3

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

1
  • DoSomething.__mro__ will show you the order in which classes are accessed when calling super with a DoSomething instances as the root object. ParentConfig.__init__ needs to call super().__init__() so that it behaves when part of a diamond inheritance MRO or you can try swapping the order of Config base classes so SomeClass comes first Commented Jan 28, 2022 at 11:51

1 Answer 1

4
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).

Sign up to request clarification or add additional context in comments.

3 Comments

Also something on the mro from Guido van Rossum: python-history.blogspot.com/2010/06/…
That was a damn good answer, thank you for this, apologies for not being able to upvote you. I think a TLDR for people newer than I am to classes would be: super stops looking if the most parent class doesn't call super().__init_() in its def __init__(self): Also, if your most parent class doesn't have a def __init__(self) then it will also be ok. My mistake was assuming super().__init__() was only needed if there was a parent class with a def __init__(self)

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.