Python partialmethod 方法绑定:预填了增量,为什么 partial 却把它放进了 self

前天 4阅读

一个计数器类想提供add_two快捷方法,最自然的写法似乎是在类里写partial(add, 2)。但实例调用它时并没有自动补入self,预填的2反而排到了最前面。问题出在类属性被取出时的绑定行为,而不是整数2不能作为默认增量。

下面程序在CPython 3.12.14执行,使用Python 3.4已有的partialmethod,不依赖新版位置占位符。保存为demo.py,运行python3 demo.py。两个计数器只保存在内存里,所有成功结果和错误路径都有断言。

Python partialmethod 方法绑定:预填了增量,为什么 partial 却把它放进了 self

AI模型生成概念图:三槽装配中先放入接收者,再放预设值和本次输入,用于比喻方法绑定时参数的位置;不是函数调用栈截图。

把三种封装放在相同函数上

add需要self和amount,并接受可选label。wrong_add_two把普通partial作为类属性,add_two则使用partialmethod。后面再从实例a已经绑定好的a.add创建partial,验证普通partial本身并没有失去用途,关键在于交给它的函数是否已经带着实例。

完整可运行程序

from functools import partial, partialmethod

class Counter:
    def __init__(self, name):
        self.name = name
        self.value = 0

    def add(self, amount, *, label="change"):
        self.value += amount
        return self.name, self.value, label

    wrong_add_two = partial(add, 2)
    add_two = partialmethod(add, 2, label="preset")

a = Counter("A")
b = Counter("B")
assert a.wrong_add_two is Counter.wrong_add_two
try:
    a.wrong_add_two()
except TypeError:
    print("class partial:", "TypeError")
else:
    raise AssertionError("plain partial unexpectedly bound self")

first = a.add_two()
second = b.add_two()
assert first == ("A", 2, "preset")
assert second == ("B", 2, "preset")
print("first instance:", first)
print("second instance:", second)
callback = a.add_two
third = callback(label="callback")
assert third == ("A", 4, "callback")
assert b.value == 2
print("saved bound callback:", third)

instance_partial = partial(a.add, 3)
fourth = instance_partial()
assert fourth == ("A", 7, "change")
print("partial of bound method:", fourth)

本次实际输出(以下为结果,不是程序)

class partial: TypeError
first instance: ('A', 2, 'preset')
second instance: ('B', 2, 'preset')
saved bound callback: ('A', 4, 'callback')
partial of bound method: ('A', 7, 'change')

普通partial没有完成实例绑定

代码先断言a.wrong_add_two就是类里保存的同一个partial对象。调用没有传入额外位置参数,因此保存的2成了add的第一个参数,amount仍缺失,最终输出class partial: TypeError。异常发生在函数主体进入之前,所以两个计数器都没有因此被修改。

修复时不能随手再补一个数字,希望调用凑够参数。那样虽然可能消除缺参错误,self却仍会是错误对象,访问self.value时继续失败。应该先确认实例怎样被绑定,而不是围绕报错不断调整参数个数。

partialmethod在属性访问时接上实例

a.add_two和b.add_two分别输出A、2、preset与B、2、preset。相同的方法定义作用在两个不同实例上,每次都有正确接收者,预填增量2再进入amount。若封装在创建类时就错误地固定了某一个实例,这个双实例测试很容易发现问题。

保存callback = a.add_two后独立调用,输出A、4、callback;b仍保持2。也就是说,从实例获取到的这份可调用对象已经带着a,后续传给其他调用位置时仍操作a。这里label的覆盖只改变标签文字,并未把接收者换成另一个计数器。

对已绑定方法使用partial是另一条合法路径

最后partial(a.add, 3)输出A、7、change,因为a.add在交给partial之前就已经携带a。普通partial此时只需要预填amount,无须再承担类级方法描述符的工作。这种形式适合临时为某个具体对象准备回调;partialmethod则适合在类定义里提供每个实例都能使用的快捷方法。

可以按两步排查:先看传入partial的对象是否来自某个实例,再看封装保存在哪里。类字典中的普通函数、实例上取出的绑定方法,以及普通partial对象,虽然都能被调用,却不具有完全相同的属性访问行为。打印调用结果时同时带实例名与状态,比只检查返回的数字更容易发现绑错对象。

绑定不是状态快照,也不是接口约束

callback第二次读取的是a当时的状态,并继续修改同一个a;它没有复制计数器。若延迟执行要求使用提交时的数字,应另行保存所需数据。相反,若希望回调始终面向最新状态,就要确保原实例在预期生命周期内仍存在,并在注销回调时解除不再需要的引用。

本文只演示普通实例方法。classmethod、staticmethod、自定义描述符和继承覆盖还可能引入不同的取值路径,不应凭这一个例子假设所有组合都一样。最小验收应包含错误类级partial、两个独立实例、提取回调和已绑定方法partial四种路径,再扩展到项目真正使用的装饰器组合。

参考资料

资料核验日期:2026年10月2日。以上输出来自文中固定输入的本地实跑,程序退出码为0。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。