Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do Python tell “this is called as a function”?

Tags:

python

A callable object is supposed to be so by defining __call__. A class is supposed to be an object… or at least with some exceptions. This exception is what I'm failing to formally clarify, thus this question posted here.

Let A be a simple class:

class A(object):

    def call(*args):
        return "In `call`"

    def __call__(*args):
        return "In `__call__`"

The first function is purposely named “call”, to make clear the purpose is the comparison with the other.

Let's instantiate it and forget about the expression it implies:

a = A() # Think of it as `a = magic` and forget about `A()`

Now what's worth:

print(A.call())
print(a.call())
print(A())
print(a())

Result in:

>>> In `call`
>>> In `call`
>>> <__main__.A object at 0xNNNNNNNN>
>>> In `__call__`

The output (third statement not running __call__) does not come as a surprise, but when I think every where it is said “Python class are objects”…

This, more explicit, however run __call__

print(A.__call__())
print(a.__call__())

>>> “In `__call__`”
>>> “In `__call__`”

All of this is just to show how finally A() may looks strange.

There are exception in Python rules, but the documentation about “object.call” does not say a lot about __call__… not more than that:

3.3.5. Emulating callable objects

object.__call__(self[, args...])

Called when the instance is “called” as a function; […]

But how do Python tell “it's called as a function” and honour or not the object.__call__ rule?

This could be a matter of type, but even type has object as its base class.

Where can I learn more (and formally) about it?

By the way, is there any difference here between Python 2 and Python 3?

----- %< ----- edit ----- >% -----

Conclusions and other experiments after one answer and one comment

Update #1

After @Veedrac's answer and @chepner's comment, I came to this other test, which complete the comments from both:

class M(type):

    def __call__(*args):
        return "In `M.__call__`"

class A(object, metaclass=M):

    def call(*args):
        return "In `call`"

    def __call__(*args):
        return "In `A.__call__`"

print(A())

The result is:

>>> In `M.__call__`

So it seems that's the meta‑class which drives the “call” operations. If I understand correctly, the meta‑class does not matter only with class, but also with classes instances.

Update #2

Another relevant test, which shows this is not an attribute of the object which matters, but an attribute of the type of the object:

class A(object):

    def __call__(*args):
        return "In `A.__call__`"

def call2(*args):
    return "In `call2`"


a = A()

print(a())

As expected, it prints:

>>> In `A.__call__`

Now this:

a.__call__ = call2

print(a())

It prints:

>>> In `A.__call__`

The same a before the attribute was assigned. It does not print In call2, it's still In A.__call__. That's important to note and also explain why that's the __call__ of the meta‑class which was invoked (keep in mind the meta‑class is the type of the class object). The __call__ used to call as function, is not from the object, it's from its type.

like image 714
Hibou57 Avatar asked Sep 10 '14 17:09

Hibou57


1 Answers

x(*args, **kwargs) is the same as type(x).__call__(x, *args, **kwargs).

So you have

>>> type(A).__call__(A)
<__main__.A object at 0x7f4d88245b50>

and it all makes sense.


chepner points out in the comments that type(A) == type. This is kind-of wierd, because type(A)(A) just gives type again! But remember that we're instead using type(A).__call__(A) which is not the same.

So this resolves to type.__call__(A). This is the constructor function for classes, which builds the data-structures and does all the construction magic.


The same is true of most dunder (double underscore) methods, such as __eq__. This is partially an optimisation in those cases.

like image 95
Veedrac Avatar answered Oct 29 '22 15:10

Veedrac