How to make super() work in this non-ideal situation in python?
class ClsOne(object):
def __init__(self):
super(ClsOne, self).__init__()
print "Here's One"
class ClsTwo(ClsOne):
def __init__(self):
super(ClsTwo, self).__init__()
print "Here's Two"
class ClsThree(ClsTwo): # Refer to one blackbox object
def __init__(self):
# super(ClsThree, self).__init__()
print "Here's Three"
class ClsThreee(ClsTwo): # Refer to your custom object
def __init__(self):
super(ClsThreee, self).__init__()
print "Here's Threee"
class ClsFour(ClsThree, ClsThreee): # Multiple Inheritance
def __init__(self):
super(ClsFour, self).__init__()
print "Here's Four"
entity = ClsFour()
In this case, you are trying to combine ClsThree (which comes from a compiled library and is difficult to change) with your own ClsThreee object. Because ClsThree forgot to call super() in its constructor, their child cannot execute ClsThreee’s constructor when using super().
As a result, the output will look like this:
Here's Three
Here's Four
Obviously, I can call each base of ClsFour manually instead of using super(), but it’s a bit complicated when this issue is scattered across my codebase.
By the way, that black box is the PySide 🙂
Addendum:
Thanks to @WillemVanOnsem and @RaymondHettinger, the previous ClsFour issue was resolved. But with some further investigation, I found that similar issues in PySide don’t have the same concept.
In the context of ClsFour, if you try to run:
print super(ClsFour, self).__init__
You will get:
<bound method ClsFour.__init__ of <__main__. ClsFour object at 0x00000000031EC160>>
But in the following PySide context:
import sys
from PySide import QtGui
class MyObject(object):
def __init__(self):
super(MyObject, self).__init__()
print "Here's MyObject"
class MyWidget(QtGui.QWidget, MyObject):
def __init__(self):
super(MyWidget, self).__init__()
app = QtGui.QApplication(sys.argv)
widget = MyWidget()
print super(MyWidget, widget).__init__
The result is:
<method-wrapper '__init__' of MyWidget object at 0x0000000005191D88>
It doesn’t print “Here’s MyObject” and the init property of super() has different types. Previously, I tried to simplify this issue to ClsFour. But now I don’t think it’s exactly the same.
I’m guessing the problem is in the shiboken library, but I’m not sure.
Hint:
This problem also appears in the context of PyQt, but you can make MyObject inherit from QObject. This solution is useless in PySide.
Solution
super(
) is a proxy object that uses method resolution order (MRO) to determine what method is called when super()
is called.
If we check the __mro__
of ClassFour
, we get:
>>> ClsFour.__mro__
(<class '__main__. ClsFour'>, <class '__main__. ClsThree'>, <class '__main__. ClsThreee'>, <class '__main__. ClsTwo'>, <class '__main__. ClsOne'>, <type 'object'>)
Or I shortened it myself (not Python output):
>>> ClsFour.__mro__
(ClsFour, ClsThree, ClsThreee, ClsTwo, ClsOne, object)
Now super(T,self
) is a proxy object that uses MRO from (but not including) T
. So this means that super(ClsFour, self)
is a proxy object, which applies to:
(ClsThree, ClsThreee, ClsTwo, ClsOne, object) # super(ClsFour,self)
If you query for a property of a class (the method is also an attribute), Python iterates through MRO and checks whether the element has such an attribute. So it will first check if ClsThree
has __init__
properties, if not, it will continue to look for it in ClsThreee
, etc. From the moment it finds such a property, it will stop and return it.
So super(ClsFour, self).__init__
will return the ClsThree.__init__
method. MRO is also used to find methods, properties, and so on that are not defined at the class level. So if you use self.x
and x is not a property of the object, nor of the ClsFour
object, it will traverse the MRO search for x
again.
If you want to refer to all __init__
as the direct parent of ClassFour
, you can use:
class ClsFour(ClsThree, ClsThreee):
def __init__(self):
# call *all* *direct* parents __init__
for par in ClsFour.__bases__:
par.__init__(self)
This is probably the most elegant, as it can still work if the base changes. Note that you must ensure that a __init__
exists for each parent. However, since it is defined at the object
level, we can safely assume this. However, for other properties, we cannot make such assumptions.
EDIT: Note that super()
does not need to point to the parent, ancestor parent, and/or ancestor of the class. But for the parent class of the object.
super(ClsThree
, self) in the ClsThree class will – assume it is a ClsFour
object, with the same mro (since it gets mro
from
self
). So super(ClsThree, self)
will check the following class sequence:
(ClsThreee, ClsTwo, ClsOne, object)
For example, if we write (beyond the scope of any class) super(ClsTwo, entity).__init__(),
we get:
>>> super(ClsTwo,entity).__init__()
Here's One
>>> super(ClsThree,entity).__init__()
Here's Two
Here's Threee
>>> super(ClsThreee,entity).__init__()
Here's Two
>>> super(ClsFour,entity).__init__()
Here's Three