Python 特殊方法查找:实例的 __len__ 已改成九十九,为什么 len 仍然返回三
测试里给一个对象临时补上__len__,直接调用能返回九十九,业务代码里的len却仍得到三。两次调用看似只是写法不同,实际上没有沿同一条属性查找路径。给实例打补丁前,要先确认调用方使用的是普通点号访问,还是语言协议。
本例在Linux、CPython 3.12.14实跑,无第三方依赖或外部资源。保存为demo.py,运行python3 demo.py。Packet保存一份列表,类上原有__len__返回列表长度;两个实例内容不同,便于观察补丁究竟影响谁。最后另用动态属性作为反例。
AI模型生成概念插图:两个对象经过同一类级测量入口,旁边的独立标记不改变公共测量路径;不是解释器界面。
完整程序与本地结果
完整可运行程序
class Packet:
def __init__(self, values):
self.values = values
def __len__(self):
return len(self.values)
first = Packet([2, 4, 6])
second = Packet([8])
first.__len__ = lambda: 99
assert first.__len__() == 99
assert len(first) == 3
print("explicit instance call:", first.__len__())
print("built-in len:", len(first), len(second))
Packet.__len__ = lambda self: len(self.values) + 10
assert (len(first), len(second)) == (13, 11)
assert first.__len__() == 99
print("after class patch:", len(first), len(second))
print("instance attribute remains:", first.__len__())
class Pretender:
def __getattr__(self, name):
if name == "__len__":
return lambda: 7
raise AttributeError(name)
pretender = Pretender()
assert hasattr(pretender, "__len__")
print("dynamic explicit call:", pretender.__len__())
try:
len(pretender)
except TypeError:
print("dynamic built-in call: TypeError")
else:
raise AssertionError("instance fallback must not supply len")本次实际输出(以下为结果,不是程序)
explicit instance call: 99 built-in len: 3 1 after class patch: 13 11 instance attribute remains: 99 dynamic explicit call: 7 dynamic built-in call: TypeError
同名属性存在,协议仍可走另一条路径
前两行分别得到显式调用99,以及内置len的3和1。对于自定义类,隐式调用特殊方法需要在对象的类型上定义相应实现;实例字典中的同名属性不能可靠替代它。first.__len__赋值成功了,只是len没有使用这一份入口。
这里的实例lambda不接收参数,因为函数放在实例字典中,显式读取后直接调用即可。调试时应同时记录直接调用结果与实际业务操作结果。如果替身只让obj.__len__()通过,而业务调用len(obj),测试检查的就是另一条路径。
类级替换会影响使用这个类的实例
替换Packet.__len__后,两个len成为13与11,说明类上的实现已被采用。first自己的同名属性依然返回99,既没有被删除,也没有自动同步。保留这个断言,能防止把结果变化误解为实例补丁终于生效。
在共享类上修改特殊方法会扩大影响范围。若只希望某个测试对象拥有不同长度行为,可创建专用测试类,或者让类方法读取实例状态。示例的类级替换只发生在独立短程序内;真实测试需要明确恢复责任,避免其他用例受到意外影响。
动态属性兜底也不能代替隐式协议
Pretender通过hasattr检查,显式调用还能返回7,但len仍抛TypeError。__getattr__是在普通属性没有找到时参与的动态兜底,它不能让这个类凭空支持所有同名隐式操作。名称能够读到,与对象实际满足协议,需要分别验收。
维护代理、包装类或测试替身时,应该使用调用方真正采用的表达式做检查。本文只验证len这一条路径,不建议把所有双下划线名称机械套成完全相同的规则;其他协议仍应查看对应语言说明。把实例内容、类定义和真实操作结果一起保存,比只查看属性清单更容易解释这种差异。
参考资料
资料核验日期:2026年10月2日。以上输出对应文中固定输入和明确运行版本,本地执行退出码为0。


