monkeypatching方法和引用的问题

时间:2011-08-26 13:14:37

标签: python monkeypatching

我想知道是否有人可以解释并提供此问题的解决方案:

$ cat object-override-methods.py 
class A:
    def foo(self):
        return 1

class B:
    def foo(self):
        return 1

for klass in A, B:
    orig_foo = klass.foo
    def foo(self):
        return orig_foo(self) * 2
    klass.foo = foo

A().foo()
B().foo()
$ python object-override-methods.py
Traceback (most recent call last):
  File "object-override-methods.py", line 15, in <module>
    A().foo()
  File "object-override-methods.py", line 12, in foo
    return orig_foo(self) * 2
TypeError: unbound method foo() must be called with B instance as first argument (got A instance instead)

提前致谢。

2 个答案:

答案 0 :(得分:2)

因为orig_foo是在全局范围内定义的,所以每次循环时都会践踏它的值。然后,每个新的foo方法都会共享这个被践踏的值。

一个简单的解决方法是将代码移动到一个函数中,如下所示:

def rebind_foo(klass):
    orig_foo = klass.foo
    def foo(self):
        return orig_foo(self) * 2
    klass.foo = foo

for klass in A, B:
   rebind_foo(klass)

这可确保每个新的foo方法都获得自己的orig_foo值。

答案 1 :(得分:2)

orig_foo是一个全局变量,每次循环都会改变值。循环完成后,orig_foo引用B.foo

内部函数foo(一个或每个遍历循环)在调用它们时都使用orig_foo的全局值。所以他们都叫B.foo(self)

当调用像orig_foo这样的“未绑定方法”时,Python2会检查第一个参数是否是相应类的实例。 A().foo()未通过此检查。 (有趣的是,这个检查在Python3中被删除了,因此不会引发TypeError,并且这个bug可能变得更难找到。)

要解决此问题,您必须将orig_foo的值绑定到相应的klass。 您可以通过将orig_foo设置为foo的局部变量来实现。一种方法是使orig_foo成为foo的参数,并使用默认值。 Python在定义函数时绑定默认值。因此orig_foo=orig_foo将局部变量orig_foo绑定到klass.foo的当前值:

for klass in A, B:
    orig_foo = klass.foo
    def foo(self, orig_foo=orig_foo):
        return orig_foo(self) * 2
    klass.foo = foo