如何修复自定义映射类中 list() 返回空列表的问题?

星枫大大_9434

星枫大大_9434

2026-11-02

951人浏览

原创

如何修复自定义映射类中 list() 返回空列表的问题?

当自定义 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 等)无缝协作的基础。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1851

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

4984

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1789

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

29997

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3567

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3587

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1223

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

616

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2783

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.1万人学习