可调用 class 的查找规则:A() vs A.__call__()

Lookup rules for callable class: A() vs A.__call__()

为了更好地理解元类,我通读了 this excellent post,遇到了一些让我感到困惑的行为。

设置

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))

问题一

A.__call__(1, 2) 打印 A.__call__, self = 1, args = (2,).

args直接转发(1出现在self的位置) 所以似乎没有描述符机制被触发。基于 these lookup rules 这可能是因为 AMeta.__call__ 是一个非数据描述符(因此不 优先于 A.__call__) 或者可能是因为 __call__ 是一个神奇的方法,因此解释器给予了一些特殊的直接查找。

我不确定这两个假设中哪一个是正确的(如果有的话)。 如果有人知道答案,我将不胜感激!

问题 2 A(1, 2) 打印 AMeta.__call__, self = <class '__main__.A'>, args = (1, 2).

元类的 __call__ 被调用,这与 AMeta__call__portrayal here 在创建 A 实例期间成为人偶大师一致.

为什么元类的 __call__ 在这里被调用?当我调用 A(1, 2) 构造函数样式时,什么查找规则在起作用?显然 A(1, 2) 不仅仅是 A.__call__(1, 2).

的语法糖

我已经看到许多其他问题围绕这些问题展开,但 none 似乎直接回答了这些问题。

我 using/concerned Python 3.7+。

问题 1:

虽然 __call__ 是一种由解释器专门处理的魔术方法,但您确实是正确的,但只有当您说 A() 并隐式委托给 [=11 时才会触发这种特殊行为=] 方法。说 A.__call__ 的执行方式与 a.foo 完全相同,这意味着它归结为,如您所说,AMeta.__call__ 是一个非数据描述符,因此被A.__call__

问题 2:

与问题 1 不同,这里确实调用了解释器对魔术方法的特殊处理。但是,包含元类会使事情复杂化一些。测试用例大致相当于下面没有元类的代码:

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))

我上面说的魔术方法的特殊处理是当试图使用像A(1, 2)这样的语法隐式调用魔术方法时,Python忽略直接在对象上设置的任何属性,只关心关于在类型上设置的属性。

因此,A(1, 2) 是粗略 type(A).__call__(A, 1, 2) 的糖分(除了忽略类型上的任何 __getattr____getattribute__ 方法这一事实)。这解释了为什么调用元类上的 __call__ 方法。