可调用 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__
方法。
为了更好地理解元类,我通读了 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__
方法。