Python Forum
Multiple inheritance - the right way ?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Multiple inheritance - the right way ?
#1
Hello,

I am not sure to fully understand multiple inheritance with Python 3. I can implement what I want to implement. It works. However, I am not sure to do things as they should be done.

My questions have to do with the constructor (namely "__new__") and the initializer (namely "__init__").

Below, I implement a (very simple) use case :

[Image: inheritance-multiple.png]

The simplified code (that shows the implementation skeleton) is :

class Parent1:

    def __new__(cls, *args, **kwargs):
        return super(__class__, cls).__new__(cls)

    def __init__(self, *args, **kwargs):
        pass

class Parent2:

    def __new__(cls, *args, **kwargs):
        return super(__class__, cls).__new__(cls)

    def __init__(self, *args, **kwargs):
        pass

class Child(Parent1, Parent2):

    def __new__(cls, *args, **kwargs):
        return super(__class__, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        Parent1.__init__(self, *args, **kwargs)
        Parent2.__init__(self, *args, **kwargs)

c = Child(1, 2, 3, name='C')
And I also give a slightly "richer" implementation, which contains code that dumps some interesting information.

Please note that the code below is, in essence, identical to the above one. The only difference between the two is that the latter produces outputs.

from pprint import pformat

class Parent1:

    def __new__(cls, *args, **kwargs):
        print("%s: Info:\n\t* __class__ = %s\n\t* cls = %s" %(__class__.__name__,
                                                              __class__.__name__,
                                                              pformat(cls)))
        print("%s: new:\n\t* %s%s%s" %(__class__.__name__,
                                       cls.__name__,
                                       (",\n\t* " if len(args) > 0 else '') + ', '.join(map(lambda x: '"%s"'%str(x), list(args))),
                                       (",\n\t* " if len(kwargs) > 0 else '') + ', '.join(map(lambda x: '%s="%s"'%(str(x[0]), str(x[1])), kwargs.items()))))
        o = super(__class__, cls).__new__(cls)
        # super(type, type2) -> bound super object; requires issubclass(type2, type)
        print("%s: cls(that is: %s) is subclass of %s ? %s" %(__class__.__name__,
                                                              cls.__name__,
                                                              __class__.__name__,
                                                              "true" if issubclass(cls, __class__) else "false"))
        print('%s: o = %s' %(__class__.__name__, pformat(o)))
        return o

    def __init__(self, *args, **kwargs):
        print('%s: self = %s' %(__class__.__name__, self))
        print('%s: init(%s%s)' %(__class__.__name__,
                                 ', '.join(map(lambda x: '"%s"'%str(x), list(args))),
                                 (', ' if len(kwargs) > 0 else '') + ', '.join(map(lambda x: '%s="%s"'%(str(x[0]), str(x[1])), kwargs.items()))))


class Parent2:

    def __new__(cls, *args, **kwargs):
        print("%s: Info:\n\t* __class__ = %s\n\t* cls = %s" %(__class__.__name__,
                                                              __class__.__name__,
                                                              pformat(cls)))
        print("%s: new:\n\t* %s%s%s" %(__class__.__name__,
                                      cls.__name__,
                                      (",\n\t* " if len(args) > 0 else '') + ', '.join(map(lambda x: '"%s"'%str(x), list(args))),
                                      (",\n\t* " if len(kwargs) > 0 else '') + ', '.join(map(lambda x: '%s="%s"'%(str(x[0]), str(x[1])), kwargs.items()))))
        o = super(__class__, cls).__new__(cls)
        # super(type, type2) -> bound super object; requires issubclass(type2, type)
        print("%s: cls(that is: %s) is subclass of %s ? %s" %(__class__.__name__,
                                                              cls.__name__,
                                                              __class__.__name__,
                                                              "true" if issubclass(cls, __class__) else "false"))
        print('%s: o = %s' %(__class__.__name__, pformat(o)))
        return o

    def __init__(self, *args, **kwargs):
        print('%s: self = %s' % (__class__.__name__, self))
        print('%s: init(%s%s)' %(__class__.__name__,
                                 ', '.join(map(lambda x: '"%s"'%str(x), list(args))),
                                 (', ' if len(kwargs) > 0 else '') + ', '.join(map(lambda x: '%s="%s"'%(str(x[0]), str(x[1])), kwargs.items()))))

class Child(Parent1, Parent2):

    def __new__(cls, *args, **kwargs):
        print("%s: Info:\n\t* __class__ = %s\n\t* cls = %s" %(__class__.__name__,
                                                              __class__.__name__,
                                                              pformat(cls)))
        print("%s: new:\n\t* %s%s%s" %(__class__.__name__,
                                       cls.__name__,
                                       (",\n\t* " if len(args) > 0 else '') + ', '.join(map(lambda x: '"%s"'%str(x), list(args))),
                                       (",\n\t* " if len(kwargs) > 0 else '') + ', '.join(map(lambda x: '%s="%s"'%(str(x[0]), str(x[1])), kwargs.items()))))
        o = super(__class__, cls).__new__(cls, *args, **kwargs)

        # o = Top1.__new__(cls, *args, **kwargs)

        # super(type, type2) -> bound super object; requires issubclass(type2, type)
        print("%s: cls(that is: %s) is subclass of %s ? %s" %(__class__.__name__,
                                                              cls.__name__,
                                                              __class__.__name__,
                                                              "true" if issubclass(cls, __class__) else "false"))
        print('%s: o = %s' %(__class__.__name__, pformat(o)))
        return o

    def __init__(self, *args, **kwargs):
        print('%s: self = %s' %(__class__.__name__, self))
        print('%s: init(%s%s)' %(__class__.__name__,
                                 ', '.join(map(lambda x: f'"%s"'%str(x), list(args))),
                                 (', ' if len(kwargs) > 0 else '') + ', '.join(map(lambda x: '%s="%s"'%(str(x[0]), str(x[1])), kwargs.items()))))

        # This is OK:
        Parent1.__init__(self, *args, **kwargs)
        Parent2.__init__(self, *args, **kwargs)


c = Child(1, 2, 3, name='C')
The output is:

Output:
$ python3 inheritance-multiple.py Child: Info: * __class__ = Child * cls = <class '__main__.Child'> Child: new: * Child, * "1", "2", "3", * name="C" Parent1: Info: * __class__ = Parent1 * cls = <class '__main__.Child'> Parent1: new: * Child, * "1", "2", "3", * name="C" Parent2: Info: * __class__ = Parent2 * cls = <class '__main__.Child'> Parent2: new: * Child Parent2: cls(that is: Child) is subclass of Parent2 ? true Parent2: o = <__main__.Child object at 0x7f5fc94b4550> Parent1: cls(that is: Child) is subclass of Parent1 ? true Parent1: o = <__main__.Child object at 0x7f5fc94b4550> Child: cls(that is: Child) is subclass of Child ? true Child: o = <__main__.Child object at 0x7f5fc94b4550> Child: self = <__main__.Child object at 0x7f5fc94b4550> Child: init("1", "2", "3", name="C") Parent1: self = <__main__.Child object at 0x7f5fc94b4550> Parent1: init("1", "2", "3", name="C") Parent2: self = <__main__.Child object at 0x7f5fc94b4550> Parent2: init("1", "2", "3", name="C")
Focusing on the constructors (__new__), you can see that:
  • Child.__new__ starts its execution.
  • Parent1.__new__ starts its execution.
  • Parent2.__new__ executes completely.
  • Parent1.__new__ terminates its execution.
  • Child.__new__ terminates its execution.

That is:

[Image: inheritance-multiple-sequence.png]


Question 1

I have tried to pass parameters to both constructors (Parent1.__new__ and Parent2.__new__). However, I can only pass parameters to one constructor. The constructor which receives the parameters is the one for the parent class that appears first in the declaration of the child class. That is:

If:

class Child(Parent1, Parent2):
        pass
Then, the contructor that receives the parameters is Parent1.__new__.

If:

class Child(Parent2, Parent1):
        pass
Then, the contructor that receives the parameters is Parent2.__new__.

I am OK with that. I understand that this is due to the "Method Resolution Order".

However:
  • why does Parent1.__new__ triggers the execution of __Parent2.new__ ? This seems weird.
  • how do I do if I want both constructor to receive parameters ?

Please note that I don't see any reason to pass parameters to the constructors since the object initialisation is done elsewhere (in the method __init__). However, if you can pass parameters to one constructor, then why shouldn't you be able to pass parameters to both constructors ?

Question 2

In order to initialise the parent classes, I use the code below:

        Parent1.__init__(self, *args, **kwargs)
        Parent2.__init__(self, *args, **kwargs)
This works. But I wondered: what if I want to use "super" instead ?

I tried:

        super(Parent1, self).__init__(*args, **kwargs)
        super(Parent2, self).__init__(*args, **kwargs)
But this strategy does not work. It throws an error.

Error:
Traceback (most recent call last): File "inheritance-multiple.py", line 94, in <module> c = Child(1, 2, 3, name='C') File "inheritance-multiple.py", line 89, in __init__ super(Parent2, self).__init__(*args, **kwargs) TypeError: object.__init__() takes no parameters
This seems weird since, the documentation for the function "super" says:

Quote:super() -> same as super(__class__, <first argument>)
super(type) -> unbound super object
super(type, obj) -> bound super object; requires isinstance(obj, type)
super(type, type2) -> bound super object; requires issubclass(type2, type)

I think that when I write:

        super(Parent1, self).__init__(*args, **kwargs)
The third use case applies:

Quote:super(type, obj) -> bound super object; requires isinstance(obj, type)

Since "self" is an instance of "Parent1". Then, the returned object should be an instance of "Parent1" bound to the current object "self".

Could you explain why the use of "super" does not work ?

Thanks a lot !

Denis
Reply


Messages In This Thread
Multiple inheritance - the right way ? - by denis_beurive - Feb-13-2019, 04:19 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Multiple Inheritance - Help pls! magnusbrigido 1 1,859 May-17-2019, 12:56 PM
Last Post: ichabod801
  Multiple Inheritance using super() Sagar 2 7,333 Sep-08-2017, 08:58 AM
Last Post: Sagar

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020