什么时候使用迭代器和发生器?

时间:2015-03-29 01:23:13

标签: python python-3.x

我读了一些关于迭代器和生成器之间差异的问题和answers。但我不明白你何时应该选择其中一个?当一个比另一个更好的时候,你知道任何例子(简单的,现实的)吗?谢谢。

1 个答案:

答案 0 :(得分: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

这是一个发电机的例子。在这种情况下,每次“调用”它都会产生Fibonacci sequence中的下一个数字。

我把“被叫”放在吓唬引号中,因为从生成器获取连续值的方法与传统函数不同。

我们有两种主要方法可以从生成器中获取值:

迭代

# 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(从generators are iterators开始)和(生成并)以这样的方式访问值:

f = fib()

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

然而,有更多有说服力的发电机的例子。这些通常以"generator expressions"的形式出现,这是一个相关的概念(PEP-289)。

考虑以下内容:

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

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

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

并将其传递给any内置函数。只要可迭代元素被确定为anyTrue就会返回True。因此,当您将生成器函数传递给any时,它只会根据需要多次调用expensive_thing(i)来查找True - ish值。

将此与使用传递给any的列表推导进行比较:

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

在这种情况下,expensive_thing(i)将调用{em>所有值i第一个,然后是100个元素列表True 1}} / 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)。