将函数分配给对象属性
根据我对Python数据模型的理解,特别是“实例方法”这一小节,每当你读取一个属性,而这个属性的值是“用户定义的函数”类型时,就会发生一些神奇的事情,你会得到一个绑定的实例方法,而不是原始的函数。这种神奇的效果就是为什么在调用方法时你不需要显式地传递self
参数。
但是,我本来以为可以用一个参数签名相同的函数来替换一个对象的方法:
class Scriptable:
def __init__(self, script = None):
if script is not None:
self.script = script # replace the method
def script(self):
print("greetings from the default script")
>>> scriptable = Scriptable()
>>> scriptable.script()
greetings from the default script
>>> def my_script(self):
... print("greetings from my custom script")
...
>>> scriptable = Scriptable(my_script)
>>> scriptable.script()
Traceback (most recent call last):
...
TypeError: script() takes exactly 1 positional argument (0 given)
我正在创建一个Scriptable
的实例,并将它的script
属性设置为一个只有一个参数的用户定义函数,这和类中定义的函数是一样的。所以当我读取scriptable.script
属性时,我本以为会有神奇的效果出现,给我一个不需要参数的绑定实例方法(就像我没有替换script
时得到的那样)。结果,它似乎返回的还是我传入的那个函数,包括self
参数。方法绑定的神奇效果没有发生。
为什么当我在类声明中定义一个方法时,方法绑定的神奇效果会生效,而当我给属性赋值时却不行?是什么让Python在这两种情况下的处理方式不同呢?
我使用的是Python3,如果这有什么影响的话。
4 个回答
看看这个:
>>> scriptable = Scriptable()
>>> scriptable.script
<bound method Scriptable.script of <__main__.Scriptable instance at 0x01209DA0>>
>>> scriptable = Scriptable(my_script)
>>> scriptable.script
<function my_script at 0x00CF9730>
这行代码 self.script = script
只是创建了一个类对象的属性,并没有什么特别的“魔法”。
而这行代码 def script(self):
在类定义里面则创建了一个描述符——这是一种特殊的对象,实际上管理着和 self
参数相关的所有内容。
你可以在提到的 Python 数据模型参考中了解更多关于描述符的内容: 实现描述符。
还有一篇关于 Python 描述符的很棒的文章,作者是 Raymond Hettinger:描述符使用指南。
感谢 Alex Martelli 在这里的 回答,这是一种不同的写法:
class Scriptable:
def script(self):
print(self)
print("greetings from the default script")
def another_script(self):
print(self)
print("greetings from the another script")
s = Scriptable()
s.script()
# monkey patching:
s.script = another_script.__get__(s, Scriptable)
s.script()
下面是你该怎么做:
import types
class Scriptable:
def __init__(self, script = None):
if script is not None:
self.script = types.MethodType(script, self) # replace the method
def script(self):
print("greetings from the default script")
正如ba__friend在评论中提到的,方法是存储在class
对象上的。当你从一个实例访问这个属性时,类对象上的描述符会返回绑定的方法。
当你把一个函数赋值给一个instance
时,并不会发生什么特别的事情,所以你需要自己把这个函数包装起来。