常见误区1. 当心那些已经枯竭的迭代器在文章前面,我们提到了使用“懒惰”生成器的种种好处 。但是,所有事物都有它的两面性 。生成器的最大的缺点之一就是:它会枯竭 。当你完整遍历过它们后,之后的重复遍历就不能拿到任何新内容了 。
-
numbers = [1, 2, 3]
-
numbers = (i * 2 for i in numbers)
-
# 第一次循环会输出 2, 4, 6
-
for number in numbers:
-
print(number)
-
# 这次循环什么都不会输出,因为迭代器已经枯竭了
-
for number in numbers:
-
print(number)
Instagram 就在项目从 Python 2 到 Python 3 的迁移过程中碰到了这个问题 。它们在 PyCon 2017 上分享了对付这个问题的故事 。访问文章 Instagram 在 PyCon 2017 的演讲摘要,搜索“迭代器”可以查看详细内容 。
2. 别在循环体内修改被迭代对象这是一个很多 Python 初学者会犯的错误 。比如,我们需要一个函数来删掉列表里的所有偶数:
-
def remove_even(numbers):
-
"""去掉列表里所有的偶数
-
"""
-
for i, number in enumerate(numbers):
-
if number % 2 == 0:
-
# 有问题的代码
-
del numbers[i]
-
numbers = [1, 2, 7, 4, 8, 11]
-
remove_even(numbers)
-
print(numbers)
-
# OUTPUT: [1, 7, 8, 11]
numbers
在循环过程中被修改了 。遍历的下标在不断增长,而列表本身的长度同时又在不断缩减 。这样就会导致列表里的一些成员其实根本就没有被遍历到 。所以对于这类操作,请使用一个新的空列表保存结果,或者利用
yield
返回一个生成器 。而不是修改被迭代的列表或是字典对象本身 。总结在这篇文章中,我们首先从“容器类型”的定义出发,在底层和高层两个层面探讨了容器类型 。之后遵循系列文章传统,提供了一些编写容器相关代码时的技巧 。
让我们最后再总结一下要点:
- 了解容器类型的底层实现,可以帮助你写出性能更好的代码
- 提炼需求里的抽象概念,面向接口而非实现编程
- 多使用“懒惰”的对象,少生成“迫切”的列表
- 使用元组和字典可以简化分支代码结构
- 使用
next
函数配合迭代器可以高效完成很多事情,但是也需要注意“枯竭”问题
- collections、itertools 模块里有非常多有用的工具,快去看看吧!
注解
- Python 这门语言除了 CPython 外,还有许许多多的其他版本实现 。如无特别说明,本文以及 “Python 工匠” 系列里出现的所有 Python 都特指 Python 的 C 语言实现 CPython
- Python 里没有类似其他编程语言里的“Interface 接口”类型,只有类似的“抽象类”概念 。为了表达方便,后面的内容均统一使用“接口”来替代“抽象类” 。
- 有没有只实现了 Mapping 但又不是 MutableMapping 的类型?试试 MappingProxyType({})
- 有没有只实现了 Set 但又不是 MutableSet 的类型?试试 frozenset
推荐阅读
- MySQL的查询性能分析神器:explain命令的使用详解
- Python爬虫:scrapy之Cookie和Session
- Python中的高阶函数
- 国内顶尖大佬整理的Python入门教程完整版!懂中文就能学会
- 一文看懂Python变量和赋值语句
- 在Python中定义Main函数
- 红衣天使怎么养 小红衣怎么养
- 怎样正确使用阿司匹林
- 买房为什么要交公共维修基金?什么情况下使用公共维修基金……
- 医保卡使用有技巧,做对这几件事报销的更多