How to monkeypatch one class's instance method to another one?

905 views Asked by At

Given a class A I can simply add an instancemethod a via

def a(self):
    pass

A.a = a

However, if I try to add another class B's instancemethod b, i.e. A.b = B.b, the attempt at calling A().b() yields a

TypeError: unbound method b() must be called with B instance as first argument (got nothing instead)

(while B().b() does fine). Indeed there is a difference between

A.a -> <unbound method A.a>
A.b -> <unbound method B.b>  # should be A.b, not B.b

So,

  • How to fix this?
  • Why is it this way? It doesn't seem intuitive, but usually Guido has some good reasons...

Curiously enough, this no longer fails in Python3...

2

There are 2 answers

1
Juh_ On BEST ANSWER

Let's:

class A(object): pass

class B(object):
    def b(self): 
        print 'self class: ' + self.__class__.__name__

When you are doing:

A.b = B.b

You are not attaching a function to A, but an unbound method. In consequence python only add it as a standard attribute and do not convert it to a A-unbounded method. The solution is simple, attach the underlying function :

A.b = B.b.__func__

print A.b
    # print: <unbound method A.b>
a = A()
a.b()
    # print: self class: A

I don't know all the difference between unbound methods and functions (only that the first contains the second), neither how all of that work internally. So I cannot explain the reason of it. My understanding is that a method object (bound or not) requires more information and functionalities than a functions, but it needs one to execute.

I would agree that automating this (changing the class of an unbound method) could be a good choice, but I can find reasons not to. It is thus surprising that python 3 differs from python 2. I'd like to find out the reason of this choice.

2
DonGar On

When you take the reference to a method on a class instance, the method is bound to that class instance.

B().b is equivalent to: lambda *args, **kwargs: b(<B instance>, *args, **kwargs)

I suspect you are getting a similarly (but not identically) wrapped reference when evaluating B.b. However, this is not the behavior I would have expected.

Interestingly:

A.a = lambda s: B.b(s)
A().a()

yields:

TypeError: unbound method b() must be called with B instance as first argument (got A instance instead)

This suggests that B.b is evaluating to a wrapper for the actual method, and the wrapper is checking that 'self' has the expected type. I don't know, but this is probably about interpreter efficiency.

It's an interesting question though. I hope someone can chime in with a more definitive answer.