这里的重要概念是类函数是一个将“self”绑定到它作为其第一个参数的函数。
我将通过几个示例进行演示。以下代码对于所有示例都是相同的:
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
到开头,*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
为plain 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)
equals的位置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
将是b, 42
。同样,这正是您所看到的。