Search code examples
pythonpython-3.xmultiple-inheritancesuperclass

The confusing action of super().__new__ and super().__init__ in multiple inheritance


Create a son class which has two parents:

class mon():
    def __new__(cls):
        print("i am in mon's new")
        return super().__new__(cls)
    def __init__(self):
        print("i am in mon's init")
        super().__init__()


class far():
    def __new__(cls):
        print("i am in far's new")
        return super().__new__(cls)
    def __init__(self):
        print("i am in far's init")


class son(mon,far):
    def __init__(self):
        super().__init__()

Initialize the son class to check what happened.
super().__init__() in son class will call __init__ method in mon,the __new__ method executed before __init__ in mon,super().__new__(cls) in mon ,it make the code jump into far class,the __new__ method executed before __init__ in far,the code jump back to mon to print i am in mon's init and jump to far to print i am in far's init ,so we get the below output.

son_instance = son()
i am in mon's new
i am in far's new
i am in mon's init
i am in far's init

issue1:

How can rewrite the three class structure to get such output as below when to initialize son class?

son_instance = son()
i am in mon's new
i am in mon's init
i am in far's new
i am in far's init

Delete the statement in far class:

return super().__new__(cls)

The whole three class is as below then:

class mon():
    def __new__(cls):
        print("i am in mon's new")
        return super().__new__(cls)
    def __init__(self):
        print("i am in mon's init")
        super().__init__()


class far():
    def __new__(cls):
        print("i am in far's new")

class son(mon,far):
    def __init__(self):
        super().__init__()

Initialize the son class again.

x=son()
i am in mon's new
i am in far's new

Issue2:
Why the code can't jump back to mon class ?Why can't get the following output?

x=son()
i am in mon's new
i am in far's new
i am in mon's init

If no return super().__new__(cls) in far class,it only take effect on __init__ in far class,but there is no __init__ method in far class at all,why it result in not calling __init__ method in mon class?


Solution

  • Let me try to answer your two questions one by one.

    How can rewrite the three class structure to get such output as below when to initialize son class?

    You can't. Your code needs to create the new instance using the __new__ methods before it can begin to initialize the instance using the __init__ methods. If you want behavior to run in some other order, you'll need to do it somewhere else. You can't adjust the inheritance hierarchy to get it to work.

    Why the code can't jump back to mon class ?Why can't get the following output?

    Your code isn't working right because far.__new__ is broken (probably because you got slightly overzealous while deleting the __init__ method and deleted one extra line). The __new__ method doesn't return anything, which is equivalent to returning None. If a __new__ method doesn't return an instance of the class that was being created, the __init__ method is never called. If you fix the __new__ method by adding back in return super().__new__(cls), you'll get the output you expect.

    That happens even though the far class doesn't have an __init__ method itself. The outer code (type.__call__ in this case) doesn't know the whole inheritance hierarchy and calls each function in turn. It only calls methods on the class that is being created, and it trusts that if it's appropriate, the implementation will call the parent class versions of the function. If son.__new__ (which is inherited) returns None as it does when far.__new__ is broken, then son.__init__ won't get called the way it did when the __new__ methods worked properly.

    Here's a very approximate pure Python version of type.call (the actual version is implemented in C):

    def __call__(cls, *args):   # this is in a metaclass, so I use cls instead of self
        obj = cls.__new__(*args)
        if isinstance(obj, cls):
            obj.__init__(*args)
        return obj
    

    There's a lot of extra stuff I left out, like how type(obj) lets you get the type of an existing instance, rather than creating a new type. But the behavior when it comes to regular instance creation works as shown.

    You can read the full details (in C) in the Python source code repository, if you want. The section where the C version of type.__call__ (called type_call) checks if the object returned from cls.__new__ is of the right type is here.