Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monkey-patching bound methods in python [duplicate]

Tags:

python

>>> 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.

like image 654
Abhishek Bhatia Avatar asked Jul 20 '16 15:07

Abhishek Bhatia


2 Answers

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.

like image 106
taleinat Avatar answered Oct 21 '22 06:10

taleinat


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).

like image 44
mgilson Avatar answered Oct 21 '22 06:10

mgilson