返回介绍

5.1 无穷数列的迭代器

发布于 2024-01-25 21:44:08 字数 1476 浏览 0 评论 0 收藏 0

如果我们只需要保存有限个状态并只发射当前值,生成器可以被用来产生无穷数列。斐波那契数列就是一个很好的例子——它是一个具有两个状态变量(最后两个斐波那契数)的无穷数列:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文