Reputation: 411
I've been reading through this excellent post in an effort to better understand metaclasses, and encountered some behavior that confuses me.
Setup
class AMeta(type):
# Disclaimer: this is not what a real-world metaclass __call__ should look like!
def __call__(self, *args):
print("AMeta.__call__, self = {}, args = {}".format(self, args))
class A(metaclass=AMeta):
def __call__(self, *args):
print("A.__call__, self = {}, args = {}".format(self, args))
Question 1
A.__call__(1, 2)
prints A.__call__, self = 1, args = (2,)
.
Args are forwarded directly (1 appears in the spot of self)
so it appears no descriptor machinery is triggered. Based on these lookup rules this could be because AMeta.__call__
is a non-data descriptor (and therefore does not
take precedence over A.__call__
) or it could be because __call__
is a magic method that is therefore given some special direct lookup by the interpreter.
I'm not sure which of these two hypothesis is correct, if either. If anyone knows the answer, I'd appreciate it!
Question 2
A(1, 2)
prints AMeta.__call__, self = <class '__main__.A'>, args = (1, 2)
.
The metaclass's __call__
gets invoked, which agrees with the portrayal here of AMeta
's __call__
being the puppet master during creation of A
instances.
Why does the metaclass's __call__
get invoked here? What lookup rule is at play when I invoke A(1, 2)
constructor-style? Clearly A(1, 2)
is not just syntactic sugar for A.__call__(1, 2)
.
I've seen a number of other questions that dance around these questions but none that appear to answer them directly.
I'm using/concerned with Python 3.7+.
Upvotes: 1
Views: 108
Reputation: 3814
Although you are indeed correct that __call__
is a magic method that is handled specially by the interpreter, that special behavior is only triggered when you say A()
and implicitly delegate to the __call__
method. Saying A.__call__
executes in exactly the same way as a.foo
, meaning that it comes down to, as you said, AMeta.__call__
being a non-data descriptor and therefore being overwritten by the explicit definition of A.__call__
Unlike in Question 1, the interpreter's special handling of magic methods does get invoked here. However, including metaclasses complicates things a little. The test case is roughly equivalent to the below code without metaclasses:
class A:
def __init__(self):
self.__call__ = self.call2
def call2(self, *args):
print("call2, self = {}, args = {}".format(self, args))
def __call__(self, *args):
print("__call__, self = {}, args = {}".format(self, args))
That special handling of magic methods I talked about above is that when trying to implicitly call magic methods using syntax like A(1, 2)
, Python ignores any attributes set directly on the object, and only cares about attributes set on the type.
Thus, A(1, 2)
is sugar for roughly type(A).__call__(A, 1, 2)
(except for the fact that any __getattr__
and __getattribute__
methods on the type are ignored). This explains why the __call__
method on the metaclass gets called.
Upvotes: 1