生成器介绍

生成器表达式类似于列表,字典和集合理解,但用括号括起来。当它们用作函数调用的唯一参数时,不必存在括号。

expression = (x**2 for x in range(10))

此示例生成 10 个第一个完美正方形,包括 0(其中 x = 0)。

生成器函数类似于常规函数,除了它们的主体中有一个或多个 yield 语句。这样的功能不能提供任何值(如果你想提前停止发电机,则允许空的 returns)。

def function():
    for x in range(10):
        yield x**2

此生成器函数等效于前一个生成器表达式,它输出相同。

注意 :所有生成器表达式都有自己的等效函数,但反之亦然。

如果两个括号都重复,则可以使用不带括号的生成器表达式:

sum(i for i in range(10) if i % 2 == 0)   #Output: 20
any(x = 0 for x in foo)                   #Output: True or False depending on foo
type(a > b for a in foo if a % 2 == 1)    #Output: <class 'generator'>

代替:

sum((i for i in range(10) if i % 2 == 0))
any((x = 0 for x in foo))
type((a > b for a in foo if a % 2 == 1))

但不是:

fooFunction(i for i in range(10) if i % 2 == 0,foo,bar)
return x = 0 for x in foo
barFunction(baz, a > b for a in foo if a % 2 == 1)

调用生成器函数会生成一个生成器对象,以后可以对其进行迭代。与其他类型的迭代器不同,生成器对象只能遍历一次。

g1 = function()
print(g1)  # Out: <generator object function at 0x1012e1888>

请注意,生成器的主体不会立即执行:在上面的示例中调用 function() 时,它会立即返回生成器对象,而不执行第一个 print 语句。这允许生成器比返回列表的函数消耗更少的内存,并且它允许创建生成无限长序列的生成器。

出于这个原因,生成器通常用于数据科学以及涉及大量数据的其他环境。另一个优点是其他代码可以立即使用生成器产生的值,而无需等待生成完整的序列。

但是,如果你需要多次使用生成器生成的值,并且生成它们的成本高于存储成本,那么将生成的值存储为 list 可能比重新生成序列更好。有关详细信息,请参阅下面的重置发电机

通常,生成器对象用于循环或任何需要迭代的函数中:

for x in g1:
    print("Received", x)

# Output:
# Received 0
# Received 1
# Received 4
# Received 9
# Received 16
# Received 25
# Received 36
# Received 49
# Received 64
# Received 81

arr1 = list(g1)
# arr1 = [], because the loop above already consumed all the values.
g2 = function()
arr2 = list(g2)  # arr2 = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

由于生成器对象是迭代器,因此可以使用 next() 函数手动迭代它们。这样做将在每次后续调用时逐个返回产生的值。

在引擎盖下,每次在生成器上调用 next() 时,Python 都会在生成器函数体中执行语句,直到它到达下一个 yield 语句。此时它返回 yield 命令的参数,并记住发生的那一点。再次调用 next() 将从该点恢复执行并继续直到下一个 yield 语句。

如果 Python 到达生成器函数的末尾而不再遇到任何 yields,则会引发 StopIteration 异常(这是正常的,所有迭代器都以相同的方式运行)。

g3 = function()
a = next(g3)  # a becomes 0
b = next(g3)  # b becomes 1
c = next(g3)  # c becomes 2
...
j = next(g3)  # Raises StopIteration, j remains undefined

请注意,在 Python 2 中,生成器对象具有 .next() 方法,可用于手动迭代生成的值。在 Python 3 中,此方法已替换为所有迭代器的 .__next__() 标准。

重置发电机

请记住,你只能迭代生成器生成的对象一次。如果你已经在脚本中迭代了对象,那么任何进一步的尝试都将产生 None

如果需要多次使用生成器生成的对象,可以再次定义生成器函数并再次使用它,或者,也可以在首次使用时将生成器函数的输出存储在列表中。如果要处理大量数据,重新定义生成器函数将是一个不错的选择,并且存储所有数据项的列表将占用大量磁盘空间。相反,如果最初生成项目的成本很高,你可能更愿意将生成的项目存储在列表中,以便你可以重复使用它们。