生成器

通过列表推导式,我们可以生成一个列表。受内存限制,列表容量有限。当创建的列表很大,而我们只取其中几个元素,就会造成很大的内存浪费。

为此生成器 generator 应运而生,在循环过程中它内部以某种算法不断计算出下一个元素。它不会一次性把所有元素列举出来,而是在使用的时候才会计算下一个元素,这样就节省了大量的空间。

生成器分为两类:

  • 生成器函数:yield 关键字
  • 生成器表达式:类似于列表推导式,但是最外层为圆括号

生成器函数

yield 语句返回函数结果的函数即 —— 生成器函数

普通函数与生成器函数的区别:

  • 普通函数顺序执行,遇到 return 或者最好一行就会返回
  • 生成器函数,在每次调用 next() 时执行,并遇到 yield 返回,再次执行从上次 yield 语句出执行
def foo():
    print('第一次打印')
    yield '第一次中断'
    print('第二次打印')
    yield '第二次中断'

f = foo()
print(f)        # <generator object foo at 0x000001DEF9121DB0>
print(next(f))
print(next(f))
print(next(f))

函数从上到下执行,遇到 yield 返回:

第一次打印
第一次中断
第二次打印
第二次中断
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-99-927644374884> in <module>()
      7 print(next(f))
      8 print(next(f))
----> 9 print(next(f))

StopIteration: 

一般我们都会使用 for 循环来遍历生成器对象:

for i in f:
    print(i)

# 第一次打印
# 第一次中断
# 第二次打印
# 第二次中断

若想获取生成器函数 return 返回值,那么就需要手动触发 StopIteration,因为 return 返回值包含在 StopIterationvalue 中:

def foo():
    print('第一次打印')
    yield '第一次中断'
    print('第二次打印')
    yield '第二次中断'
    return '生成器返回值'
    
f = foo()

while True:
    try:
        x = next(f)
        print('f', x)
    except StopIteration as e:
        print('生成器 return 返回值:', e.value)
        break
第一次打印
f 第一次中断
第二次打印
f 第二次中断
生成器 return 返回值: 生成器返回值

示例

用生成器函数实现斐波拉契数列

# max 为最大个数
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
f = fib(6)

for i in f:
    print(i)        # 1、1 、2 、3 、5 、8

生成器表达式

创建一个生成器表达式很简单,只需将列表推导式最外层的中括号 [],换成圆括号 () 即可。

l = [x*x for x in range(3)]     # 列表推导式

g = (x*x for x in range(3))     # 创建生成器表达式:生成器对象
print(l)    # [0, 1, 4]

print(g)    # <generator object <genexpr> at 0x000001DEF9121F10>

通过 next() 获取生成器中每个元素:

next(g)     # 0
next(g)     # 2
next(g)     # 4
next(g)     # 超出边界,触发 StopIteration
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-92-e9fc50c01225> in <module>()
      6 next(g)
      7 next(g)
----> 8 next(g)

StopIteration: 

一般都不会使用 next() 函数调用生成器下一个元素,而是使用 for 循环,这样也不会触发 StopIteration

for i in g:
    print(i)    # 0/1/4

总结

  • 生成器实现了迭代器协议,它是可迭代对象
  • 可以左右 for 循环,也可以使用 next() 函数调用,一次只能取一个
  • 分为生成器函数(yield)和生成器表达式
  • 生成器函数遇到 yield 即返回,下一次调用 next(),从上次 yield 处继续执行
  • 节省内存空间
posted @ 2019-03-04 16:41  Hubery_Jun  阅读(161)  评论(0编辑  收藏  举报