猴子补丁:将类中的方法替换为函数
我有一些傻乎乎的代码,其中有个猴子匹配的部分。下面的例子只是为了自学,并不是用在实际项目中。
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()
当我执行这段代码时,我遇到了以下错误:
TypeError: non_class_some_method() missing 1 required positional argument: 'self'
很明显,Python解释器不能隐式地把obj
传递给some_function
。但是当我进行代码检查并查看字节码时,我发现方法的表现形式是相似的(替换前后都是这样)。
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))
结果是:
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)
请问有没有人能解释一下为什么会这样?
1 个回答
4
大部分答案在这里:https://wiki.python.org/moin/FromFunctionToMethod。简单来说:
def
关键字总是会生成一个function
对象。method
其实就是一个很薄的可调用的包装器,它把函数、类和实例包裹起来。- 这个包装器只有在函数是类的属性时才会被创建,而不是在它是实例的属性时。
为了让你的代码正常工作(在每个实例上进行猴子补丁),你需要手动调用那个可以把函数、类和实例变成 method
的机制。当使用新式类时,最简单的解决办法是直接在函数上调用描述符协议,也就是:
obj.some_method = some_function.__get__(obj, type(obj))
如果你使用的是像你例子中那样的旧式类(这其实不是个好主意),你可以使用 types.MethodType
,但实际上,除非你被一些遗留代码困住了,否则最好把你的类改成新式类(也就是从 object
继承)。