It's not clear to me how the typical metaclass singleton implementation works. I would expect the starred print to execute twice; it only happens once:
class _Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
print('Within __call__', cls, cls._instances, *args, **kwargs)
if cls not in cls._instances:
print('**About to call __call__', super(_Singleton, cls).__call__, flush=True)
print("Is cls the '...of object'?", hex(id(cls)).upper())
cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
print(cls._instances[cls])
return cls._instances[cls]
class MySingleton(metaclass=_Singleton):
pass
if __name__ == '__main__':
print('Making mysingleton')
mysingleton = MySingleton()
print("Is mysingleton the 'cls'?", mysingleton)
print('Making another')
another = MySingleton()
# Verify singletude
assert another == mysingleton
This prints
Making mysingleton
Within __call__ <class '__main__.MySingleton'> {}
**About to call __call__ <method-wrapper '__call__' of _Singleton object at 0x000001C950C28780>
Is cls the '...of object'? 0X1C950C28780
<__main__.MySingleton object at 0x000001C9513FCA30>
Is mysingleton the 'cls'? <__main__.MySingleton object at 0x000001C9513FCA30>
Making another
Within __call__ <class '__main__.MySingleton'> {<class '__main__.MySingleton'>: <__main__.MySingleton object at 0x000001C9513FCA30>}
As is my experience with the Python docs, they're terribly circular and confusing. The docs say that __call__()
is called when an instance is "called". Fair enough; MySingleton.__call__
is run because the "()" on mysingleton = MySingleton()
indicates a function call. MySingleton is of the _Singleton type, so it has an _instances dict. The dict is naturally empty on first call. The conditional fails and Super(_Singleton, cls).__call__
executes.
What does super() do here? It's barely intelligible from the docs. It returns a "proxy object", explained elsewhere as "an object which 'refers' to a shared object", that delegates method calls to a parent or sibling class of 'type'. Okay, fine; it will be used to call a method of some related _Singleton type.
The two argument form of super(), which is used here, "specifies the arguments exactly and makes the appropriate references". What references are those? The type is _Singleton
and object is cls, which isn't mysingleton
. It's whatever object 0x000001C950C28780
is. Anyway, the super() search order is that of getattr()
or super()
. I think that means references are looked up according to _Singleton.__mro__
since __call__
isn't an attribute (or is it?). That is, the super() call looks up according to super(), which I assume is _Singleton. Clear as mud. The __mro__
yields (<class '__main__._Singleton'>, <class 'type'>, <class 'object'>)
. So, super(_Singleton, cls)
will look for the "related _Singleton type" and call its __call__
method; I assume that's cls.__call__()
.
Since cls is a _Singleton, I would expect to see the second print. Actually, I would expect some kind of recursion. Neither happen. What's going on in there?