Reputation: 68
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
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