Python __call__() is this an implicit classmethod?
Asked Answered
E

2

6

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.

Escharotic answered 22/6, 2017 at 16:5 Comment(1)
The code does not work if I pass self: how did you 'pass self' here? If you renamed cls to self (everywhere in the function, not just the parameter), the code would continue to work. Hell, rename it to gabbledygook and it'll work.Jassy
J
13

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.

Jassy answered 22/6, 2017 at 16:7 Comment(0)
N
0

Martin's answer says it all. I am adding this so maybe a different wording can throw in more light for different people:

Python call() is this an implicit classmethod?

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 classmethods. Thats is why the super().__new__ call above needs to include the metacls as the first parameter)

Norbert answered 23/6, 2017 at 8:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.