pythonsingletonnew-operatorinitderived

Python: __init__ of derived Singleton not called


I was toying around with the Singleton pattern and derivation. Specifically, I had this code:

class Singleton:
    _instance = None
    init_attempts = 0

    def __init__(self):
        self.init_attempts += 1
    
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls, *args, **kwargs)
        return cls._instance


class Derived(Singleton):
    def __init__(self):
        super().__init__()
        self.attribute = "this is derived"


def main():
    instance_1 = Singleton()
    instance_2 = Singleton()

    print("instance_1 and instance_2 are ", end="")
    if id(instance_1) == id(instance_2):
        print("the ame")
    else:
        print("different")
    
    derived_instance = Derived()
    print("derived_instance and instance_2 are the same:", derived_instance is instance_2)
    
    try:
        print(derived_instance.attribute)
    except AttributeError:
        print("Initialization of Derived has been prevented")
    
    print("Number of initializations:", derived_instance.init_attempts)


if __name__ == '__main__':
    main()

To my surprise, the __init__ of Derived is never called, i.e. derived_instance.attribute does not exist and derived_instance.init_attempts remains 2. As I understand it, SomeClass() is resolved to SomeClass.__call__(), which in turn should call the __new__ and __init__ methods. Yet, in the above example, Derived.__init__ is never called.

In detail, my output is:

instance_1 and instance_2 are the ame
derived_instance and instance_2 are the same: True
Initialization of Derived has been prevented
Number of initializations: 2

Can anybody explain to me, why that is? In particular, the similar example here: How to initialize Singleton-derived object once works as expected, i.e. Tracer triggers printing init twice.

I am aware that deriving from Singletons cannot be done in a meaningful way, and that both, a Singleton decorator and metaclass exist and how to use them, so I'm specifically interested in how the inheritance process goes awry. I presume it has something to do with the MRO, but I cannot think of anything that would not also compromise the Tracer example from the link.

I tried finding similar links, but could not find any additional information.

This here asks a very similar question: does __init__ get called multiple times with this implementation of Singleton? (Python) but doesn't answer it, or rather only confirms that a derived class' ´__init__´ should get called as well.

Thanks in advance! Hoping to pay back the favour soon!


Solution

  • The Python documentation has the answer:

    If __new__() is invoked during object construction and it returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to the object constructor.

    If __new__() does not return an instance of cls, then the new instance’s __init__() method will not be invoked.

    In the case at hand, when Derived() (which has no __new__ of its own) is called to create a class instance, Singleton.__new__ is invoked, and because that does not return an instance of Derived (but of Singleton), the __init__() method will not be invoked.