Reputation: 16730
Python's callables are objects that have a __call__
method.
They are most of the time functions, but might be class instances as well.
But it so happens that functions do have a __call__
method.
Therefore, a __call__
method has a __call__
method as well.
The following REPL session shows that we can chain the __call__
s:
>>> print
<built-in function print>
>>> print.__call__
<method-wrapper '__call__' of builtin_function_or_method object at 0x0000025E2D597F78>
>>> print.__call__.__call__
<method-wrapper '__call__' of method-wrapper object at 0x0000025E2F631438>
>>> print.__call__.__call__.__call__
<method-wrapper '__call__' of method-wrapper object at 0x0000025E2F5F85F8>
>>> print.__call__.__call__.__call__.__call__
<method-wrapper '__call__' of method-wrapper object at 0x0000025E2F725DA0>
... and so on. Remarkably, all these methods have different addresses. In addition, they all have the same behaviour:
>>> print("a")
a
>>> print.__call__("a")
a
>>> print.__call__.__call__("a")
a
>>> print.__call__.__call__.__call__("a")
So, when I write print("a")
, what is actually called?
Is it print
, or print.__call__
?
And what if I define a Foo
class with a __call__
method?
Furthermore, how is it possible that every __call__
method has its own different __call__
method?
Could it be that they are in fact created when I tried to access them?
Upvotes: 4
Views: 2093
Reputation: 1123400
Like methods on classes, the __call__
attribute is a descriptor object defined on the type, bound to the object you looked it up on:
>>> type(print).__dict__['__call__']
<slot wrapper '__call__' of 'builtin_function_or_method' objects>
>>> type(print).__dict__['__call__'].__get__(print)
<method-wrapper '__call__' of builtin_function_or_method object at 0x10cc66f78>
>>> print.__call__
<method-wrapper '__call__' of builtin_function_or_method object at 0x10cc66f78>
The binding behaviour (through the __get__
method) is how the resulting method-wrapper
instance knows to pass in the print
object as self
, just like the instance gets passed into methods you defined on a custom Python class.
So yes, that means they are created on demand, and thus will have a unique id. They are otherwise just more instances of the same type.
Upvotes: 3
Reputation: 152725
print
is an instance of the class builtin_function_or_method
:
>>> type(print)
builtin_function_or_method
As we all know (hopefully) when you access a method the instance is implicitly passed as first argument:
>>> type(print).__call__(print, 10)
10
>>> print.__call__(10) # equivalent
10
That means you actually have something like a bound method when you access __call__
of a function. However bound methods (or in this case methodwrappers) are also instances of the type method_wrapper
:
>>> type(print.__call__)
method-wrapper
So when you access the __call__
method of that one it's again a "new" bound method.
>>> type(print.__call__).__call__(print.__call__, 10) # equivalent
10
At this point it becomes recursive because a method of a method-wrapper is just another method-wrapper instance:
>>> type(print.__call__.__call__)
method-wrapper
However all that does it just creates a lot of "unnecessary" bound methods...
Upvotes: 3