Python 特殊方法查找:实例的 __len__ 已改成九十九,为什么 len 仍然返回三

前天 3阅读

测试里给一个对象临时补上__len__,直接调用能返回九十九,业务代码里的len却仍得到三。两次调用看似只是写法不同,实际上没有沿同一条属性查找路径。给实例打补丁前,要先确认调用方使用的是普通点号访问,还是语言协议。

本例在Linux、CPython 3.12.14实跑,无第三方依赖或外部资源。保存为demo.py,运行python3 demo.py。Packet保存一份列表,类上原有__len__返回列表长度;两个实例内容不同,便于观察补丁究竟影响谁。最后另用动态属性作为反例。

Python 特殊方法查找:实例的 __len__ 已改成九十九,为什么 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。

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