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!
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.