Let's walk through a (corrected) definition of Singleton
and a class defined using it. I'm replacing uses of cls
with Singleton
where the lookup is passed through anyway.
class Singleton(type):
_instances = {}
# Each of the following functions use cls instead of self
# to emphasize that although they are instance methods of
# Singleton, they are also *class* methods of a class defined
# with Singleton
def __call__(cls, *args, **kwargs):
if cls not in Singleton._instances:
Singleton._instances[cls] = super().__call__(*args, **kwargs)
return Singleton._instances[cls]
def clear(cls):
try:
del Singleton._instances[cls]
except KeyError:
pass
class MySing(metaclass=Singleton):
pass
s1 = MySing() # First call: actually creates a new instance
s2 = MySing() # Second call: returns the cached instance
assert s1 is s2 # Yup, they are the same
MySing.clear() # Throw away the cached instance
s3 = MySing() # Third call: no cached instance, so create one
assert s1 is not s3 # Yup, s3 is a distinct new instance
First, _instances
is a class attribute of the metaclass, meant to map a class to a unique instance of that class.
__call__
is an instance method of the metaclass; its purpose is to make instances of the metaclass (i.e., classes) callable. cls
here is the class being defined, not the metaclass. So each time you call MyClass()
, that converts to Singleton.__call__(MyClass)
.
clear
is also a instance method of the metaclass, meaning it also takes a instance of the meta class (i.e again, a class) as an argument (not an instance of the class defined with the metaclass.) This means MyClass.clear()
is the same as Singleton.clear(MyClass)
. (This also means you can, but probably shouldn't for clarity, write s1.clear()
.)
The identification of metaclass instance methods with "regular" class class methods also explains why you need to use __call__
in the meta class where you would use __new__
in the regular class: __new__
is special-cased as a class method without having to decorate it as such. It's slightly tricky for a metaclass to define an instance method for its instances, so we just use __call__
(since type.__call__
doesn't do much, if anything, beyond invoking the correct __new__
method).