- 内容提要
- 前言
- 作者简介
- 封面简介
- 第1章 理解高性能 Python
- 第2章 通过性能分析找到瓶颈
- 2.1 高效地分析性能
- 2.2 Julia 集合的介绍
- 2.3 计算完整的 Julia 集合
- 2.4 计时的简单方法——打印和修饰
- 2.5 用 UNIX 的 time 命令进行简单的计时
- 2.6 使用 cProfile 模块
- 2.7 用 runsnakerun 对 cProfile 的输出进行可视化
- 2.8 用 line_profiler 进行逐行分析
- 2.9 用 memory_profiler 诊断内存的用量
- 2.10 用 heapy 调查堆上的对象
- 2.11 用 dowser 实时画出变量的实例
- 2.12 用 dis 模块检查 CPython 字节码
- 2.13 在优化期间进行单元测试保持代码的正确性
- 2.14 确保性能分析成功的策略
- 2.15 小结
- 第3章 列表和元组
- 第4章 字典和集合
- 第5章 迭代器和生成器
- 第6章 矩阵和矢量计算
- 第7章 编译成 C
- 第8章 并发
- 第9章 multiprocessing 模块
- 第10章 集群和工作队列
- 第11章 使用更少的 RAM
- 第12章 现场教训
5.1 无穷数列的迭代器
如果我们只需要保存有限个状态并只发射当前值,生成器可以被用来产生无穷数列。斐波那契数列就是一个很好的例子——它是一个具有两个状态变量(最后两个斐波那契数)的无穷数列:
def fibonacci(): i, j = 0, 1 while True: yield j i, j = j, i + j
这里可以看到,虽然j是那个被发射的值,我们依然需要保留i的记录,因为它保存了斐波那契数列的状态。生成器计算所需要的状态的数量非常关键,因为它最终会被转化成对象的内存足迹。如果我们有一个函数需要使用很多的状态,却输出很少的数据,那么使用预先计算的列表可能比生成器更合适。
生成器并没有想象中使用的那么广泛的一个原因是它们的逻辑可以被包含在你的代码中。这意味着生成器其实是用更聪明的循环组织你代码的一种方式。比如,我们可以有多种方式回答问题“5000以内的斐波那契数中有几个奇数?”:
def fibonacci_naive(): i, j = 0, 1 count = 0 while j <= 5000: if j % 2: count += 1 i, j = j, i + j return count def fibonacci_transform(): count = 0 for f in fibonacci(): if f > 5000: break if f % 2: count += 1 return count from itertools import islice def fibonacci_succinct(): is_odd = lambda x : x % 2 first_5000 = islice(fibonacci(), 0, 5000) return sum(1 for x in first_5000 if is_odd(x))
所有这些方法都有近似的运行时属性(也就是说它们都有相同的内存足迹和同样的性能),但fibonacci_transform函数具有多个优势。首先,它看上去比fibonacci_succinct直观很多,可以轻易被另一个开发者调试和理解。关于后者的警告见下一节,我们会讨论一些itertools的常见用法——该模块在大大简化了很多迭代器的简单操作的同时也会迅速让Python代码变得不那么像Python。另一方面,fibonacci_naive一次做了多件事情,隐藏了它实际的计算逻辑!而使用斐波那契数列的生成器函数则不会阻碍我们理解实际的计算逻辑。最后,fibonacci_transform可以变得更加通用。这个函数可以被重命名为num_ odd_under_5000并接受一个生成器参数,这样它就可以工作在任意数列上。
Fibonacci_transform函数的最后一个好处是它标注了计算的两个阶段:数据的生成和数据的转化。该函数很明显是在进行数据的转化,而fibonacci函数则是生成数据。这一界限的划分增加了额外的清晰度和功能:我们可以让一个转化函数工作在一组新的数据上,或在一组数据上进行多个转化。这一规范在创建复杂程序时十分重要,而使用生成器则可以明确地表示:生成器用于创建数据,而普通函数则操作生成的数据。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论