生成器

生成器是一种使用普通函数语法定义的迭代器.

创建生成器

生成器创建起来与函数一样简单.

包含yield语句的函数都被称为生成器. 这不仅仅是名称上的差别, 生成器的行为与普通函数截然不同. 差别在于, 生成器不是使用return返回一个值, 而是可以生成多个值, 每次一个. 每次使用yield生成一个值后, 函数都将冻结, 即在此停止执行, 等待被重新唤醒. 被重新唤醒后, 函数将从停止的地方开始继续执行.

例如, 定义一个生成某个范围内浮点数的生成器:

def frange(start, stop, step):
    x = start
    while x < stop:
        yield x
        x += step

>>> for n in frange(0, 4, 0.5):
...     print(n)
...
0
0.5
1.0
1.5
2.0
2.5
3.0
3.5

和普通函数不同的是, 生成器只能用于迭代操作. 下面是一个实验, 展示生成器函数底层的工作机制:

>>> def countdown(n):
...     print('Starting to count from', n)
...     while n > 0:
...         yield n
...         n -= 1
...     print('Done!')
...

>>> # Create the generator, notice no output appears
>>> c = countdown(3)
>>> c
<generator object countdown at 0x1006a0af0>

>>> # Run to first yield and emit a value
>>> next(c)
Starting to count from 3
3

>>> # Run to the next yield
>>> next(c)
2

>>> # Run to next yield
>>> next(c)
1

>>> # Run to next yield (iteration stops)
>>> next(c)
Done!
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
StopIteration
>>>

一个生成器函数的主要特征是它只回应在迭代中使用到的next操作. 一旦生成器函数返回退出, 迭代终止. 我们在迭代中通常使用的for语句会自动处理这些细节, 所以你无需担心.

生成器是包含关键字yield的函数, 但被调用时不会执行函数体内的代码, 而是返回一个迭代器. 每次请求值时, 都将执行生成器的代码, 直到遇到yieldreturn. yield意味着应生成一个值, 而return意味着生成器应停止执行(即不再生成值; 仅当在生成器调用return时, 才能不提供任何参数).

换而言之, 生成器由两个单独的部分组成: 生成器函数生成器的迭代器. 生成器的函数是由def语句定义的, 其中包含yield. 生成器的迭代器是这个函数返回的结果.

Example:

对于生成器的函数返回的迭代器, 可以像使用其它迭代器一样使用它.

生成器的方法

  • send()
  • throw()

用于在生成器中(yield表达式处, 即挂起时)引发异常, 调用时可提供一个异常类型, 一个可选值和一个traceback对象. 之后程序会继续执行生成器函数中后续的代码, 直到遇到下一个yield语句. 如果剩余代码执行完毕也没有遇到下一个yield语句, 则程序会引发StopIteration异常.

Example:

def foo():
    try:
        yield 1
    except ValueError:
        print('Capture ValueError')

f = foo()
print(next(f))
f.throw(ValueError)

# 运行结果:
1
Capture exception
Traceback (most recent call last):
File "/tmp/snow/generator.py", line 9, in <module>
    f.throw(ValueError)
StopIteration

一开始执行生成器函数在yield 1处挂起, 当执行throw()方法时, 它会先抛出ValueError异常, 然后继续执行后续代码直到下一个yield语句, 该程序后续代码中不再有yield语句, 因此程序执行到最后, 抛出StopIteration异常.

  • close()

用于停止生成器, 调用时无需提供任何参数.

方法close也是基于异常的: 在yield处引发GeneratorExit异常. 因此, 如果要在生成器中提供一些清理代码, 可将yield放在一条try/finally语句中. 如果愿意, 也可捕获GeneratorExit异常, 但随后必须重新引发它(可能在清理后), 引发其它异常或直接返回. 对生成器调用close后, 再试图从它那里获取值将导致RuntimeError异常.

生成器推导式

生成器推导式, 其工作原理和列表推导式相同, 但不是创建一个列表(即不立即执行), 而是返回一个生成器, 让你能够逐步进行计算.

Example:

>>> g = ((i + 2) ** 2 for i in range(2, 27))
>>> next(g)
16
>>> next(g)
25

可以看到, 不同于列表推导式, 生成器推导式的定义使用的是圆括号.

直接在一对既有的圆括号内(如在函数调用中)使用生成器推导式时, 无需再添加一对圆括号. 换而言之, 可编写下面这样非常漂亮的代码:

sum(i ** 2 for i in range(1))