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?
Most of the answer is here : https://wiki.python.org/moin/FromFunctionToMethod. To make a long story short:
def
always yield a function
objectmethod
is just a thin callable wrapper around the function, class and instanceTo 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
).