通过列表推导式,我们可以生成一个列表。受内存限制,列表容量有限。当创建的列表很大,而我们只取其中几个元素,就会造成很大的内存浪费。
为此生成器 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
返回值包含在 StopIteration
的 value
中:
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 处继续执行
- 节省内存空间