为什么生成器表达式只能迭代一次?

时间:2016-09-04 14:43:58

标签: python

这个程序:

def pp(seq):
    print(''.join(str(x) for x in seq))
    print(''.join(str(x) for x in seq))
    print('---')

pp([0,1,2,3])
pp(range(4))  # range in Python3, xrange in Python2
pp(x for x in [0,1,2,3])

打印出来:

0123
0123
---
0123
0123
---
0123

---

这是container.__iter__iterator.__iter__之间差异的结果。两者都记录在这里:https://docs.python.org/3/library/stdtypes.html#typeiter。如果__iter__每次都返回一个新的迭代器,我们会看到两条重复的行。如果它每次返回相同的迭代器,我们只看到一行,因为迭代器在此之后已经耗尽。

我的问题是为什么决定实现生成器表达式与其他似乎相同或至少非常相似的对象不相似?

这是生成器表达式与其他类似类型不同的另一个示例,可能会导致意外结果。

def pp(seq):
    # accept only non-empty sequences
    if seq:
        print("data ok")
    else:
        print("required data missing")

pp([])
pp(range(0))
pp(x for x in []) 

输出:

required data missing
required data missing
data ok

2 个答案:

答案 0 :(得分:3)

生成器运行任意代码,并返回一个延迟序列,其中包含该代码产生的项目。

  • 该代码可以提供无限的序列。
  • 该代码可以通过网络连接读取内容。
  • 每次迭代时,代码都可以修改外部变量。

因此,您无法在一般情况下安全地缓存结果:如果它是一个无限序列,那么您的内存就会耗尽。

您还可以在重新读取代码时重新运行代码:如果它正在读取网络连接,则连接可能不再存在再次在未来(或可能处于不同的状态)。同样,如果代码修改了genexp范围之外的变量,那么该状态将以难以预测的方式根据读者来改变。行为 - 对于重视可预测性和可读性的语言而言,这是一种不合需要的行为。

其他一些语言(特别是Clojure)实现生成器(在那里,通用为"延迟序列")当且仅当对序列开头的引用时才缓存结果保留。这为程序员提供了控制,但代价是复杂:您需要确切知道垃圾收集器的确切内容和未引用内容。

决定不为Python做这件事是完全合理的,并且符合语言的设计目标。

答案 1 :(得分:1)

  

我的问题是为什么决定实现与其他似乎相同或至少非常相似的对象不相似的生成器表达式?

因为它确实是一个生成器,如果你使它与其他迭代相似,你必须保留内存中的所有项目,然后你拥有的不再是生成器。

使用生成器的主要好处是它们不会占用内存而只是按需生成项目,这使得它们成为一次性迭代,因为否则如果您希望能够迭代它们多次你必须在内存中保存它们。