当Python __call__方法获得额外的第一个参数时?

时间:2019-07-20 14:45:59

标签: python-3.x python-datamodel

以下示例

import types
import pprint
class A:
    def __call__(self, *args):
        pprint.pprint('[A.__call__] self=%r, args=%r'
                      % (self, list(args)))
class B:
    pass
if __name__ == '__main__':
    a = A()
    print(callable(a))
    a(1, 2)
    b = B()
    b.meth = types.MethodType(a, b)
    b.meth(3, 4)

打印

True
'[A.__call__] self=<__main__.A object at 0xb7233c2c>, args=[1, 2]'
('[A.__call__] self=<__main__.A object at 0xb7233c2c>, args=[<__main__.B '
 'object at 0xb71687cc>, 3, 4]')

__ call__方法参数的数量在 b.meth(3,4)示例。请解释第一个(__main __。B 对象...)以及Python何时提供它?

在Debian 9.9 Stretch上使用Python 3.5.3

1 个答案:

答案 0 :(得分:2)

这里的重要概念是类函数是将“自身”绑定为第一个参数的函数。

我将通过几个示例进行演示。对于所有示例,以下代码均相同:

import types

# Class with function
class A:
    def func(*args):
        print('A.func(%s)'%(', '.join([str(arg) for arg in args])))

# Callable function-style class
class A_callable:
    def __call__(*args):
        print('A_callable.__call__(%s)'%(', '.join([str(arg) for arg in args])))

# Empty class
class B():
    pass

# Function without class
def func(*args):
    print('func(%s)'%(', '.join([str(arg) for arg in args])))

现在让我们考虑几个示例:

>>> func(42)
func(42)

这很明显。它仅使用参数func调用函数42

下一个更有趣:

>>> A().func(42)
A.func(<__main__.A object at 0x7f1ed9ed2908>, 42)
>>> A_callable()(42)
A_callable.__call__(<__main__.A_callable object at 0x7f1ed9ed28d0>, 42)

您会看到类对象self作为第一个参数被自动赋予函数。重要的是要注意,没有添加self参数,因为该功能存储在一个对象中,但由于该功能是作为对象的一部分而被构造的,因此未添加 ,因此将对象绑定到了

演示:

>>> tmp = A().func
>>> tmp
<bound method A.func of <__main__.A object at 0x7f1ed9ed2978>>
>>> tmp(42)
A.func(<__main__.A object at 0x7f1ed9ed2978>, 42)

>>> tmp = A_callable().__call__
>>> tmp
<bound method A_callable.__call__ of <__main__.A_callable object at 0x7f1ed9ed2908>>
>>> tmp(42)
A_callable.__call__(<__main__.A_callable object at 0x7f1ed9ed2908>, 42)

添加self参数是,因为您在其前面写了a.。它是函数对象本身的一部分,将其存储在变量中仍然保持该绑定。

您还可以手动将类对象绑定到函数,如下所示:

>>> tmp = types.MethodType(func, B)
>>> tmp
<bound method func of <class '__main__.B'>>
>>> tmp(42)
func(<class '__main__.B'>, 42)

另一方面,仅将函数分配给类不会self绑定到该函数。如前所述,该参数在调用时不会动态添加,但在构造时会静态添加:

>>> b = B()
>>> b.func = func
>>> b.func
<function func at 0x7f1edb58fe18>
>>> b.func(42)
func(42) # does NOT contain the `self` argument

这就是为什么要向函数添加self的显式绑定的原因,

>>> b = B()
>>> b.func = types.MethodType(func, b)
>>> b.func
<bound method func of <__main__.B object at 0x7f1ed9ed2908>>
>>> b.func(42)
func(<__main__.B object at 0x7f1ed9ed2908>, 42)

剩下的唯一一件事就是了解绑定的工作原理。如果方法func绑定了参数a,并被*args调用,它将在{的开头中添加a {1}},然后将其传递给函数。 开始在这里很重要。


现在我们知道理解您的代码所需的一切:

*args

首先,我们可以将>>> a = A_callable() >>> b = B() >>> b.func = types.MethodType(a, b) >>> b.func <bound method ? of <__main__.B object at 0x7f1ed97e9fd0>> >>> b.func(42) A_callable.__call__(<__main__.A_callable object at 0x7f1ed97fb2b0>, <__main__.B object at 0x7f1ed97e9fd0>, 42) 更改为普通b.func,因为如前所述,向对象添加功能不会改变其类型或功能。仅绑定tmp

然后,让我们一步一步地遍历代码:

self

到目前为止,一切都很好。我们有一个空对象>>> a = A_callable() >>> b = B() 和一个可调用对象b

a

这是症结所在。如果您了解这一点,您将了解所有内容。

>>> tmp = types.MethodType(a,b) 现在是绑定了tmp的函数a。这意味着,如果我们调用b,它会将tmp(42)添加到其参数的开始处。 b将因此收到a。然后,由于b, 42是可调用的,因此它将其参数转发到a

这意味着,我们到了a.__call__等于tmp(42)的地步。

由于a.__call__(b, 42)__call__的类函数,因此A_callable在构建a时会自动绑定到__call__函数。因此,在参数到达a之前,A_callable.__call__被添加到参数列表的开始,这意味着参数现在为a

现在我们到了a, b, 42等于tmp(42)的地步。这正是您所看到的:

A_callable.__call__(a, b, 42)

现在,如果将参数拆分为>>> tmp = types.MethodType(a, b) >>> tmp(42) A_callable.__call__(<__main__.A_callable object at 0x7f1ed97fb2b0>, <__main__.B object at 0x7f1ed97e9fd0>, 42) >>> A_callable.__call__(a, b, 42) A_callable.__call__(<__main__.A_callable object at 0x7f1ed97fb2b0>, <__main__.B object at 0x7f1ed97e9fd0>, 42) ,则基本上只需取出第一个参数并将其存储在self, *args中。您的第一个参数是self,因此a将是self,而其他a将是*args。同样,这正是您所看到的。