Reputation: 85
I want to implement a singleton pattern in python, and I liked the pattern described in the http://www.python-course.eu/python3_metaclasses.php.
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class SingletonClass(metaclass=Singleton):
pass
class RegularClass():
pass
x = SingletonClass()
y = SingletonClass()
print(x == y)
x = RegularClass()
y = RegularClass()
print(x == y)
And the code works perfect. But, the __call__()
does not have the self
, and it also does not have @classmethod
or @staticmethod
declaration.
But, in the Python data model https://docs.python.org/3/reference/datamodel.html#object.__call__ the __call__()
method has a self in the arguments.
The code does not work if I pass self
, or declare as @staticmethod
or @classmethod
.
Can someone please explain the logic of the syntax behind the __call__()
method.
Upvotes: 6
Views: 6697
Reputation: 110156
Martin's answer says it all. I am adding this so maybe a different wording can throw in more light for different people:
No. But all "metaclass" methods are implicit "class methods" for the classes that use that metaclass.
That is implicit when we take in account the fact that classes are simply instances of the metaclass. From the language point of view, a class behave almost exactly like any other instance - and any interactions with a class that would trigger dunder (__magic__
) methods in an object - like using the "+, -, *, /" operators, or index retrieval with [ ]
, or calling them with ( )
trigger the corresponding methods on its class. That is ordinarily type
.
And, as put in the other answer, Python does not care what name you ut on the first argument of a method. For metaclasses it makes sense to use cls
there, since the "instances" the methods are dealing with are metaclasses. As it makes sense that the first argument to a metaclass' __new__
method be named metacls
(like in the example bellow). Because the new "cls" is the object we get after calling type.__new__
- the only call possible in pure Python that will actually create a class object.
class Meta(type):
def __new__(metacls, name, bases, namespace):
...
cls = super().__new__(metacls, name, bases, namespace)
...
return cls
(Now, still on the topic of the question: __new__
is a special case
of an implicit static method (even for ordinary classes that are not intended to be metaclasses) - to which Python specially add the first argument, using a different mechanism than what is done to regular classmethod
s. Thats is why the super().__new__
call above needs to include the metacls as the first parameter)
Upvotes: 0
Reputation: 1121256
Naming the first argument of a method cls
or self
are just a convention. The __call__
method does have a self argument, only it is named cls
here. That's because for a metaclass, the method is bound to a class object, and the name reflects this.
The same convention is applied to @classmethod
methods; the first argument is a class, always, due to the nature of how a classmethod
object is bound, so it makes sense to name that first argument cls
.
But you are free to name that first argument anything else. It is not the name that makes a classmethod or a regular method or a method on a metatype work. All that using self
or cls
does is document what type of object this is, making it easier for other developers to mentally track what is going on.
So no, this is not an implicit class method. That first argument is not bound to the Singleton
metaclass object, it is bound to the class that was called. That makes sense, because that class object is an instance of the Singleton
metatype.
If you want to dive into how binding works (the process that causes that first argument to be passed in, whatever the name), you can read up on the Descriptor HOWTO. TLDR: functions, property
, classmethod
and staticmethod
objects are all descriptors, and whenever you access them as an attribute on a supporting object such as an instance or a class, they are bound, often causing a different object to be returned as a result, which when called passes in the bound object to the actual function.
Upvotes: 13