实现 __len__ 方法使类支持 len(),需返回非负整数,每次调用应反映真实状态,避免耗时操作与缓存失效问题,返回值必须为 int 类型。

让类支持 len() 的核心是实现 __len__
Python 中 len() 函数不直接读取属性,而是调用对象的 __len__ 方法。只要你在类里定义了这个特殊方法,并返回一个非负整数,len(obj) 就能正常工作。
关键点在于:__len__ 必须返回 int,且不能是负数(否则会抛 ValueError: __len__() should return >= 0)。它本身不存储长度,只负责“此刻算出多少”。
常见错误是试图在 __len__ 里缓存结果但没处理状态变更,导致长度滞后于真实状态。
__len__ 如何响应外部状态变化
所谓“外部状态”,通常指:另一个对象、全局变量、文件内容、数据库查询结果、或本类中某个被其他代码修改的属性(比如 self._data 是别人塞进来的列表)。
此时 __len__ 应该每次调用都重新计算,而不是依赖内部缓存——除非你同时控制状态变更入口并主动失效缓存。
- 如果外部状态是可变对象(如传入的
list或dict),直接在__len__中调用其len()即可 - 如果外部状态是异步/延迟加载的(如网络响应、文件大小),
__len__里要能处理可能的异常(但注意:__len__抛异常会导致len()失败,慎用) - 避免在
__len__中做重 IO 或复杂计算——用户调用len()时通常预期是 O(1) 操作
示例:
class Wrapper:
def __init__(self, data_source):
self.data_source = data_source # 外部可变对象,比如一个 list
def __len__(self):
return len(self.data_source) # 每次都查真实长度
当外部状态不可直接 len() 时怎么办
比如状态是字符串 ID、URL、路径名,真实长度需查数据库或文件系统——这时 __len__ 就不该承担这个职责。
更合理的做法是:把“获取长度”显式拆成一个方法(如 get_actual_length()),而 __len__ 只返回确定、轻量的结果(比如已知的缓存值,或抛 TypeError 表明不支持)。
- 强制在
__len__中做耗时操作,会让所有依赖len()的逻辑(如if obj:、for循环判断)变得不可预测 - 若必须支持,至少加一层简单缓存 + 时间戳或版本号校验,避免重复计算
- 文档里明确写清
__len__的行为代价,否则使用者会误以为它是廉价操作
反例(不推荐):
def __len__(self):
# 每次都发起 HTTP 请求 —— 隐蔽且昂贵
resp = requests.get(self.url)
return len(resp.json())
容易被忽略的兼容性细节
__len__ 返回值类型必须严格是 int。返回 numpy.int64、float、甚至 int 子类(若未正确实现 __index__)都可能在某些上下文中出错(比如切片、range() 构造)。
- 用
return int(...)显式转换最安全 - 不要在
__len__中 raiseNotImplementedError或静默返回 0——这会让调用者无法区分“无长度”和“空” - 如果类逻辑上确实没有明确长度(比如流式生成器封装),最好不实现
__len__,而不是返回占位值
真正复杂的场景往往不是“怎么写 __len__”,而是“该不该让它反映外部状态”。多数时候,把长度计算逻辑外置、保持 __len__ 简洁可预测,反而更少踩坑。










