在python中修补绑定方法

Monkey-patching bound methods in python

本问题已经有最佳答案,请猛点这里访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> class A:
...     def foo(self):
...             print(self)
...
>>>
>>> a = A()
>>> a.foo()
<__main__.A instance at 0x7f4399136cb0>
>>> def foo(self):
...     print(self)
...
>>> a.foo = foo
>>> a.foo()
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 argument (0 given)

我试着理解猴子在Python中的修补。请说明错误的原因以及如何修复错误。


如本回答中所述,您需要在执行此操作时使用types.MethodType或类似的东西,例如:

1
a.foo = types.MethodType(foo, a)

原因是a.foo = foo只是将函数foo设置为a的一个属性,没有进行"绑定魔法"。要让python在调用a.foo时"神奇地"将实例作为第一个参数传递,您需要告诉python进行这种绑定,例如使用types.MethodType进行绑定。

有关更多详细信息,请参阅以上链接的答案。


所以这里的棘手之处在于,你得到的取决于方法所处的位置:

1
2
3
4
5
6
7
8
9
10
11
12
class A(object):
    def foo(self):
        print("Hello world")


def patch(self):
    print("patched!")


print(type(A.foo))
a = A()
print(type(a.foo))

如果运行此命令,在python2.x和3.x上会得到不同的结果:

1
2
3
4
5
6
$ python ~/sandbox/test.py  # python2.x
<type 'instancemethod'>
<type 'instancemethod'>
$ python3 ~/sandbox/test.py  # python3.x
<class 'function' at 0x100228020>
<class 'method' at 0x10021d0c0>

但在这两种情况下,很明显,a.foo是某种方法。

如果我们试着用猴子修补它会发生什么?

1
2
a.foo = patch
print(type(a.foo))  # <type 'function'> (2.x) / <class 'function'> (3.x)

好,现在我们看到a.foofunction类型(不是一种方法)。所以问题是我们如何从"补丁"中创造出一种方法?答案是我们在将其作为属性添加时使用其描述符协议:

1
a.foo = patch.__get__(a, A)

对于一个类上的方法,当您执行a.some_method时,python实际上执行了:a.some_method.__get__(a, type(a)),所以我们只是在这里(显式地)复制调用序列。