何时使用迭代器和何时使用生成器?

3

我阅读了一些关于迭代器和生成器之间区别的问题和答案。但是我不明白何时应该选择其中之一?您知道任何例子(简单的、现实生活中的)在某些情况下一个比另一个更好吗?谢谢。

我阅读了一些有关迭代器和生成器之间区别的问题和答案。但是我不明白何时应该选择其中之一?您是否知道任何例子(简单的、现实生活中的),其中一种方法比另一种方法更好?谢谢。

4
阅读该帖子后,您具体哪些内容不理解? - jkd
@AlexMartelli,希望我的评论不太离题,我在开始自己的评论之前没有阅读相关的问题/答案 - 而且我应该有这样做。 (此外,如果可以的话,这个链接似乎是你可能感兴趣的内容,如果你感到无聊:)) - jedwards
我是Python的新手。在我看来,大多数东西都是新的。对于一个新手来说,简单的例子比写“Python的生成器提供了实现迭代器协议的便捷方式”或其他技术难题更好。 - clappersturdy
需要一些技术术语的知识,你掌握得越多,就越能找到问题的答案并提出更好的问题以获得更好的答案。回答你的问题,请查看Vincent Driessen关于迭代器与生成器问题的文章。 - LinkBerest
正如我在五年前的那个(最受欢迎但仍未被接受的:-)答案中所解释的,所有生成器都是迭代器(尽管反之不成立),因此你的问题绝对没有任何意义——就像问“何时应该吃意大利面而不是通心粉”一样,因为意大利面通心粉的一个例子(实例,特殊情况),或者问“我应该买拉布拉多还是狗”(因为拉布拉多狗的品种),等等。 - Alex Martelli
简洁性,genexp -> generator。可扩展性,灵活性 -> 迭代器。 - Shashank
1个回答

4

迭代器提供了在现有数据结构上进行迭代的高效方法。

生成器提供了即时生成序列元素的高效方法。

迭代器示例

Python的文件阅读器可以用作迭代器。因此,您可能会使用以下内容来处理文件的一行:

with open('file.txt', 'rb') as fh:
    lines = fh.readlines()  # this reads the entire file into lines, now
    for line in lines:
        process(line)       # something to be done on each line

你可以使用迭代器更高效地实现。
with open('file.txt', 'rb') as fh:
    for line in fh:         # this will only read as much as needed, each time
        process(line)

优点在于第二个例子中,您不会将整个文件读入内存,然后迭代行列表。相反,读取器(Python3中的BufferedReader)每次您要求一行时都会读取一行。

生成器示例

生成器可以即时生成序列元素。请考虑以下内容:

def fib():
    idx  = 0
    vals = [0,1]
    while True:
        # If we need to compute a new value, do so on the fly
        if len(vals) <= idx: vals.append(vals[-1] + vals[-2])
        yield vals[idx]
        idx += 1

这是生成器的一个例子。在这种情况下,每次“调用”它时,它都会产生斐波那契数列中的下一个数字。
我用引号括起了“调用”,因为获取生成器连续值的方法与传统函数不同。
我们有两种主要方法来从生成器中获取值:
通过迭代
# Print the fibonacci sequence until some event occurs
for f in fib():
    print(f)
    if f > 100: break

在这里,我们使用in语法来迭代生成器,并打印返回的值,直到我们得到一个大于100的值。

输出:

0
1
1
2
3
5
8
13
21
34
55
89
144

调用next()

我们也可以在生成器上调用next(因为生成器是迭代器),以这种方式(生成并)访问值:

f = fib()

print(next(f))  # 0
print(next(f))  # 1
print(next(f))  # 1
print(next(f))  # 2
print(next(f))  # 3

然而,有更有说服力的生成器示例。这些通常采用"生成器表达式"的形式,这是一个相关概念(PEP-289)。

考虑以下内容:

first = any((expensive_thing(i) for i in range(100)))

在这里,我们正在创建一个生成器表达式:

(expensive_thing(i) for i in range(100))

并将其传递给内置函数any。当可迭代对象的元素被确定为True时,any会立即返回True。因此,当您将生成器函数传递给any时,它只会调用expensive_thing(i)尽可能少的次数来查找True值。
与使用传递给any的列表推导进行比较:
first = any([expensive_thing(i) for i in range(100)])

在这种情况下,将为所有的i值调用expensive_thing(i),首先,然后将100个元素的True/False值列表提供给any,如果它找到一个True-ish值,它将返回True
但是,如果expensive_thing(0)返回True,显然更好的方法只是评估它,测试它,并在那里停止。生成器允许您这样做,而类似列表推导的东西则不行。
考虑以下例子,说明使用生成器表达式比列表推导式更有优势:
import time

def expensive_thing(n):
    time.sleep(0.1)
    return 10 < n < 20

# Find first True value, by using a generator expression
t0 = time.time()
print( any((expensive_thing(i) for i in range(100))) )
t1 = time.time()
td1 = t1-t0

# Find first True value, by using a list comprehension
t0 = time.time()
print( any([expensive_thing(i) for i in range(100)]) )
t1 = time.time()
td2 = t1-t0

print("TD 1:", td1)  # TD 1:  1.213068962097168
print("TD 2:", td2)  # TD 2: 10.000572204589844

函数expensive_thing引入了人为的延迟,以说明这两种方法之间的差异。第二种(列表推导式)方法需要更长的时间,因为expensive_thing所有100个索引处都被评估,而第一个方法只调用expensive_thing直到找到一个True值(i=11)。

1
Python 3.x中的range和Python 2.x中的xrange不是生成器。它们是惰性序列。生成器的定义是产生迭代器的函数。请参见https://docs.python.org/3/glossary.html#term-generator。 - user2555451
2
为了理解概念,它们可以完全被视为生成器。由于这些内置函数可能是提问者熟悉的,我使用了它们 - 但我会在我的帖子中更新一个正确的生成器。 - jedwards

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接