"Create singleton class in python by taking advantage of meta class"
don't.
please.
This defines overkill.
So, before explaining what went wrong or your doubt, here is how you can define a singleton in Python, which can only be usd as a singleton, unless someone decides to use introspection to "create more than one singleton"- at which point the person could just as well abuse whatever thr mechanisms you have there.
Simply create a class, then create the instance that will be your singleton. Then remove the reference to the class from the namespace.
If there is code that would try to instantiate the class that should be a singleton, put a __call__
method returning self
:
class MySingleton():
...
def __call__(self):
return self
mysingleton = MySingleton()
del MySingleton
If the code won't expect to instantiate the class, or ou need the singleton to be callable for other reasons: this __call__
is simply cosmetic. Actually, the singleton instance could have the same name as the class, and even the del
statement is not needed.
Now, forthe part you got confused in your code: as far as any method in the metaclass is concerned, if it is a method that would be an "instance" method: i.e. take "self" as the first parameter, that first parameter refers to a class! (not the metaclass). So, we usually write cls
as the parameter name when writting methods for the metaclass.
In other words, in the case of your __call__
that "cls" is the instance of the metaclass: the classes themselves. Using the name cls
for the parameter in __call__
does not magically makes it receive a reference to the class where the method is written (the metaclass itself).
If given your class you want to get a reference to the metaclass, just proceed as with any other Python object: use either the type
call or check the __class__
attribute.
So the fix for your code would be, if you choose to ignore my advice to have metaclasses just for having a singleton:
def __call__(cls, *args, **kwargs):
if cls not in cls._instance:
# usually a lock is used here, but omitted for simplicity
cls.__class__._instance[cls] = super().__call__(*args, **kwargs)
return cls._instance[cls]
If you want the first parameter in a metaclass method to be the metaclass itself, the @classmethod
decorator does that(but such modifier can only be applied for ordinay methods - other than "dunder" special methods, like __call__
- the language expects __call__
and others to take the instance as first parameter)