>>> 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.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With