>>> 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)
I am trying to understand monkey-patching in Python. Please illustrate the reason for the error and how to fix it.
2 Answers 2
As described in this SO answer, you need to use types.MethodType or something similar when doing this, e.g.:
a.foo = types.MethodType(foo, a)
The reason is that a.foo = foo just sets the function foo as an attribute of a - no "binding magic" is done. To have Python "magically" pass the instance as the first argument when calling a.foo, you need to tell Python to do such binding, e.g. by using types.MethodType.
See the above linked answer for (much) more details.
Comments
So the tricky thing here is that what you get depends on where the method lives:
class A(object):
def foo(self):
print("Hello world")
def patch(self):
print("patched!")
print(type(A.foo))
a = A()
print(type(a.foo))
If you run this, you'll get different results on python2.x and 3.x:
$ python ~/sandbox/test.py # python2.x
<type 'instancemethod'>
<type 'instancemethod'>
$ python3 ~/sandbox/test.py # python3.x
<class 'function' at 0x100228020>
<class 'method' at 0x10021d0c0>
But in either case it's clear that a.foo is a method of some sort.
What happens if we try to monkey patch it?
a.foo = patch
print(type(a.foo)) # <type 'function'> (2.x) / <class 'function'> (3.x)
Ok, now we see that a.foo is of type function (not a method). So the question is how do we make a method out of out "patch"? The answer is we use it's descriptor protocol when adding it as an attribute:
a.foo = patch.__get__(a, A)
For a method on a class, when you do a.some_method, python actually does: a.some_method.__get__(a, type(a)) so we're just reproducing that call sequence here (explicitly).
4 Comments
A.foo = patch; a = A(); a.foo() -- However, if you're really going to be doing a lot of patching, I hope it's just in tests and I'd advise you use a mocking library to do it (e.g. mock)unittest.mock (which can be downloaded/installed using pip if you're on python2.x). Obviously it's good to know how these things work for the occasions when you need them, but most of the time, you can use the library and not worry about the details.