将函数分配给对象属性

24 投票
4 回答
10817 浏览
提问于 2025-04-16 20:16

根据我对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 个回答

7

看看这个:

>>> 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:描述符使用指南

11

感谢 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()
18

下面是你该怎么做:

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时,并不会发生什么特别的事情,所以你需要自己把这个函数包装起来。

撰写回答