
当自定义 MutableMapping 类错误地将自身实现为迭代器(即 __iter__ 返回 self 且实现了 __next__),会导致 list(obj) 等内置函数因迭代器状态被提前耗尽而返回空列表;正确做法是让 __iter__ 返回独立、可重复使用的迭代器。
当自定义 `mutablemapping` 类错误地将自身实现为迭代器(即 `__iter__` 返回 `self` 且实现了 `__next__`),会导致 `list(obj)` 等内置函数因迭代器状态被提前耗尽而返回空列表;正确做法是让 `__iter__` 返回独立、可重复使用的迭代器。
问题根源在于:你的 SideDict 同时实现了 __iter__(返回 self)和 __next__,使其成为自身即迭代器的对象。而 Python 内置函数如 list()、len() 或 tuple() 在消费迭代器前,常会先调用 __len__() —— 而你的 __len__() 又通过 [k for k in self] 主动触发了一次完整迭代。由于迭代器状态(如 _iter, _iter_in_side_dict, _iter_keys_seen)是实例共享的,第一次迭代(在 __len__ 中)已将内部迭代器推进至末尾并引发 StopIteration;随后 list() 尝试继续调用 __next__() 时,直接再次抛出 StopIteration,导致结果为空列表 []。
这不是 list() 的 bug,而是“自迭代器”模式与 Python 迭代协议不兼容的典型表现:一个迭代器只能被完全消费一次,而 list() 和 len() 需要各自独立、互不干扰的遍历能力。
✅ 正确解法:让 __iter__ 返回全新、无状态、可复用的迭代器,而非复用 self。推荐两种简洁可靠的实现方式:
方式一:使用生成器函数(推荐)
语义清晰、内存友好、无需额外类,且天然支持多次调用:
def __iter__(self):
# 先遍历主字典所有键
yield from self._store.keys()
# 再补充 side_dict 中主字典不存在的键(保持覆盖逻辑)
if self._side_dict is not None:
for key in self._side_dict:
if key not in self._store:
yield key方式二:基于视图运算构造一次性迭代器
利用 dict.keys() 的集合操作特性(注意:仅适用于纯键遍历,且需确保 side_dict 键类型一致):
def __iter__(self):
if self._side_dict is None:
return iter(self._store.keys())
# 使用 | 实现键的并集(自动去重,保留主字典优先级)
return iter(self._store.keys() | self._side_dict.keys())⚠️ 同时务必移除所有与迭代状态相关的实例变量(如 _iter_keys_seen, _iter_in_side_dict, _iter),因为它们不再需要——每次 __iter__() 调用都应从头开始、彼此隔离。
此外,__len__() 应彻底重构,避免依赖 for k in self(这又会触发迭代):
def __len__(self):
if self._side_dict is None:
return len(self._store)
# 主字典键数 + side_dict 中不在主字典里的键数
side_only_keys = sum(1 for k in self._side_dict if k not in self._store)
return len(self._store) + side_only_keys? 额外建议:除非是教学或有特殊约束,否则请直接使用标准库中的 collections.ChainMap。它已高效实现多层映射的“就近查找”语义,支持任意嵌套、不可变视图、以及正确的迭代与长度行为,且经过充分测试:
from collections import ChainMap
sd_0 = ChainMap({"a": "A"})
sd_1 = ChainMap({"b": "B"}, sd_0)
sd_2 = ChainMap({"c": "C"}, sd_1)
print(list(sd_0)) # ['a']
print(list(sd_1)) # ['b', 'a'] —— 符合预期!
print(len(sd_2)) # 3总结:永远避免让通用容器类(如 dict 子类、Mapping 实现)成为自身的迭代器。遵循“可迭代对象 ≠ 迭代器”原则——__iter__ 应返回新迭代器,而非 self。这不仅是修复 list() 的关键,更是保证与 Python 生态(for 循环、推导式、itertools 等)无缝协作的基础。

















