3

I have some silly code with monkey matching part in it. The example below is only for self-studying not for production.

class MyClass:

    def some_method(self):
        print("some_method call")
        self.yet_another_method()

    def yet_another_method(self):
        print('yet_another_method call')


def some_function(self):
    print("some function call")
    self.yet_another_method()

obj = MyClass()
obj.some_method()
obj.some_method = some_function
obj.some_method()

When I execute this code I get the following error:

TypeError: non_class_some_method() missing 1 required positional argument: 'self'

It's obvious that Python interpreter can't implicitly pass obj in some_function. But when I doing introspection and getting the byte code I have the similar representation of method (before and after replace).

import dis
import inspect


class MyClass:

    def some_method(self):
        print("some_method call")
        self.yet_another_method()

    def yet_another_method(self):
        print('yet_another_method call')


def some_function(self):
    print("some function call")
    self.yet_another_method()

obj = MyClass()
dis.dis(obj.some_method)
print(inspect.getargspec(obj.some_method))
obj.some_method = some_function
print("======================================================================")
dis.dis(obj.some_method)
print(inspect.getargspec(obj.some_method))

The result:

  8           0 LOAD_GLOBAL              0 (print) 
              3 LOAD_CONST               1 ('some_method call') 
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
              9 POP_TOP              

  9          10 LOAD_FAST                0 (self) 
             13 LOAD_ATTR                1 (yet_another_method) 
             16 CALL_FUNCTION            0 (0 positional, 0 keyword pair) 
             19 POP_TOP              
             20 LOAD_CONST               0 (None) 
             23 RETURN_VALUE         
ArgSpec(args=['self'], varargs=None, keywords=None, defaults=None)
======================================================================
 16           0 LOAD_GLOBAL              0 (print) 
              3 LOAD_CONST               1 ('some function call') 
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
              9 POP_TOP              

 17          10 LOAD_FAST                0 (self) 
             13 LOAD_ATTR                1 (yet_another_method) 
             16 CALL_FUNCTION            0 (0 positional, 0 keyword pair) 
             19 POP_TOP              
             20 LOAD_CONST               0 (None) 
             23 RETURN_VALUE         
ArgSpec(args=['self'], varargs=None, keywords=None, defaults=None)

Please can anyone explain why it happens this way?

Mind Mixer
  • 1,503
  • 3
  • 13
  • 15
  • Well this happens because functions defined on instances are never converted to bound methods, they should be defined as class attribute.(Or you can use [`types.MethodType`](https://docs.python.org/2/library/types.html#types.MethodType) to register a function as method using instance.) – Ashwini Chaudhary Jul 23 '14 at 14:40
  • If you only want to change the method for only one instance of a class , not for all instances of the class then your question is answered here: http://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object?rq=1 – Ross Ridge Jul 23 '14 at 16:45

1 Answers1

4

Most of the answer is here : https://wiki.python.org/moin/FromFunctionToMethod. To make a long story short:

  • def always yield a function object
  • a method is just a thin callable wrapper around the function, class and instance
  • this wrapper is only created when the function is an attribute of the class - not when it's an attribute of the instance.

To make your code work (monkeypatching on a per-instance basis), you have to manually invoke the mechanism that would create a method from the function, class and instance. When using new-style classes the simplest solution is to directly invoke the descriptor protocol on the function, ie:

obj.some_method = some_function.__get__(obj, type(obj))

When using old-style classes like you do in your example (and which is not a good idea), you can use types.MethodType instead, but really unless you are stuck with some legacy code you'd be better making your class a new-style one (=> inherit from object).

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118