<aside> 💡 如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

</aside>

生成器创建方式

  1. 方括号改成圆括号

    >>> L = [x * x for x in range(10)]
    >>> L
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    >>> g = (x * x for x in range(10))
    >>> g
    <generator object <genexpr> at 0x1022ef630>
    
  2. 函数定义中包含关键字 yield ,这个函数不是普通函数,而是生成器函数。调用一个生成器函数将返回生成器。

    >>> f = fib(6)
    >>> f
    <generator object fib at 0x104feaaa0>
    

打印 generator 的每一个元素

  1. 使用 next() 函数

    next(g)
    # 每次调用会计算 g 的下一个值,没有更多的元素会抛异常
    
  2. 使用 for 循环

    >>> g = (x * x for x in range(10))
    >>> for n in g:
    ...     print(n)
    

斐波那契数列

  1. 函数实现

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b
            n = n + 1
        return 'done'
    
  2. 生成器函数实现

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            yield b
            a, b = b, a + b
            n = n + 1
        return 'done'
    

生成器函数

生成器函数每次调用 next() 函数执行,遇到 yield 语句返回,再次执行的时候从上次返回的 yield 语句处继续执行。

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

<aside> ⚠️ 调用 generator 函数会创建一个 generator 对象,多次调用 generator 函数会创建多个相互独立的 generator

例:

>>> next(odd())
step 1
1
>>> next(odd())
step 1
1
>>> next(odd())
step 1
1

每次调用都会返回 1,原因就在于上面的代码创建了三个独立的 generator。正确写法如下:

>>> g = odd()
>>> next(g)
step 1
1
>>> next(g)
step 2
3
>>> next(g)
step 3
5

</aside>

用 for 循环来迭代

>>> for n in fib(6):
...     print(n)
...
1
1
2
3
5
8

generator 的返回值包含在 StopIterationvalue 中。

>>> g = fib(6)
>>> while True:
...     try:
...         x = next(g)
...         print('g:', x)
...     except StopIteration as e:
...         print('Generator return value:', e.value)
...         break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done