Python – How to make super() work in this non-ideal situation in python?

How to make super() work in this non-ideal situation in python?… here is a solution to the problem.

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

Related Problems and Solutions