__init__ versus __call__

So, I was thinking…

What would be different if I don’t override __init__ in a class definition but I rather override __call__? Would I be eligible to the same behaviour or functionality through an instance of that class that __init__ provides?

Let me override both in a class definition and then create an instance of that class to see what I get if I make them both print something out.

>>> class MyClass:
...     def __init__(self):
...         print("I was called!")
...     def __call__(self):
...         print("Uhm, was I called?")
...
>>> MyClass()
I was called!
<__main__.MyClass object at 0x000001B291307460>

So, you can see that __init__ prints out its message I was called!, but __call__ didn’t print out its Uhm, was I called? message.

Des anyone have any idea why is that the case?

__call__ allows the instance to be called; try MyClass()().

2 Likes

When MyClass is called, the __call__() method is looked up on its metaclass, type. This can be overridden with a custom metaclass, but it’s rarely needed. For example:

class MyMetaClass(type):
    def __call__(self, *args, **kwds):
        print(self, 'called')
        return super().__call__(*args, **kwds)
 
class MyClass(metaclass=MyMetaClass):
    def __init__(self):
        print(self, 'initialized')
>>> o = MyClass()
<class '__main__.MyClass'> called
<__main__.MyClass object at 0x7fdfb9a5cb10> initialized

The __call__ dunder method is for controlling the behaviour when you call an instance of the class, not the class itself.

obj = MyClass()  # calls MyClass.__new__ and MyClass.__init__

result = obj()  # calls MyClass.__call__

3. Data model — Python 3.10.2 documentation.new

3. Data model — Python 3.10.2 documentation.call

Just like obj[index] calls MyClass.__getitem__.

The above behaviour is controlled by the class of MyClass, called the metaclass. But changing the metaclass is extremely advanced and you should avoid it unless you really need it.

I think your answer is by far the most simple depending on all the subsequent answers here.

Since everything is an object in Python – what thing enables a function call to be made? A class object has this __call__ thing for the job. What thing does a function object have?

What thing enables me to do my_function() in a module-level scope and to then get some result?

A function object is an instance of types.FunctionType, whose __call__ method runs the code of the function.

To simulate a function, don’t worry about the complicated metaclass business. Just give your class a __call__ method, and use an instance as a function-like object:


class MyFunction:

    def __call__(self, arg):

        return arg + 1



func = MyFunction()

result = func(10)  # returns 11

We call func a callable. Its not really a function, because it is not an instance of the built-in function type. But it acts like a function.

Actual functions work the same way. They are instances of a class, types.FunctionType, and they have a __call__ dunder method that knows how to call the function.

To me it appeared that you were trying to override what MyClass() does, which requires overriding __call__() in the metaclass. If one is actually trying to implement or override __call__() for the instance, then I don’t see why one would need to be told to use MyClass()(). If one wants to call an instance, then one has to first get an instance, which means one has to call the class.

Exactly how a call expression is implemented will depend on the implementation of the Python language. If an object isn’t callable, however, then trying to call it fails with a TypeError regardless of the implementation.

For CPython, the function type’s __call__() method is implemented as the PyFunction_Type.tp_call slot, which is a pointer to a function that’s written in the C programming language. Generically, CPython calls a callable object via PyObject_Call(callable, args, kwds), which in general calls the tp_call slot of the type. For a function object, it normally doesn’t do that. Instead the interpreter accesses the function’s argument names, default values, closure cells, and compiled bytecode to directly evaluate a call in a frame. The implementation details can get complicated for the sake of efficiency and performance (e.g. frame reuse, vector calls, adaptive calls), but understanding such details doesn’t really help with understanding Python.

The special methods are always looked up on the type, not on the instances. Classes specify behavior of their instances, not of themselves, with special methods. C.__add__ says how instances of C are added, not how C is added (to something else).