上一章我们介绍了列表生成式。通过列表生成式,我们可以直接创建一个可以按照某种算法求得的 list。在两种情况下,列表生成式会显得不是很合理。
第一,当 list 占用内存很大时。如果一个 list 种有几百万的元素,那么该列表会占用很大空间,这时如果直接用列表生成式生成,那么显得不太合适。
第二,当 list 元素很多,但大多数情况下只访问前面几个元素时。这时如果使用列表生成式,不仅浪费内存空间,生成过程也浪费时间。
综上,列表生成式就会显得无力。既然我们的列表可以按照某种算法求得,那么我们是否可以只把算法保存下来,之后在需要的时候通过循环推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建一个generator,有两个重要的方法。下面分开介绍:
- 把列表生成器中的 [] 改为 (),代码如下:
generator有两种迭代方法,一种是调用方法 next(generator g);另一种是 for 循环,因为generator也是可迭代对象。# 列表生成器 L = [x * x for x in range(5)] # 结果如下 # [0, 1, 4, 9, 16] # 生成器 g = (x * x for x in range(5)) print(g) # 结果如下,表明 g 是一个generator # <generator object <genexpr> at 0x0000025D8B40A258>
(1) 迭代方法一: next(generator g)
该方法的缺点显而易见。我们不知道一个生成器中元素的个数,只能尝试性的调用 next() 试探,当抛出 StopIteration 的错误时,结束迭代。# 调用方法 next(g) 迭代生成器 g = (x * x for x in range(5)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g)) # 结果如下 # 0 1 4 9 16 # Traceback (most recent call last): # File "D:Python/Python基础/test.py", line 12, in <module> # print(next(g)) # StopIteration
(2) 迭代方法二:for循环# for循环迭代一个生成器 g = (x * x for x in range(5)) for x in g: print x # 结果如下 # 0 1 4 9 16
- 如果一个函数定义中包含 yield 关键字,那么这个函数就不再是一个普通函数,而是generator。
这里需要介绍一下关键字 yield。生成器和函数的不同在于执行流程不同:函数是顺序执行,当遇到return时返回;而生成器则不同,生成器再每次调用 next(generator g) 时被执行,当遇到 yield 时返回后面的值并停止,再次调用next(generator g) 从上次返回的yield语句处继续执行。# 定义一个求裴波那契数列的生成器 # max 表示最大数列下标,n 表示目前计算到的位置,a 和 b 均用与计算 def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done'
如果需要return后面的返回值,方法如下:# 函数式生成器使用 for x in fib(6): print(x) # 结果如下 # 1 1 2 3 5 8
# 获取 return 后面的值的方法 while True: try: x = next(g) print(x) except StopIteration as e: print('Generator return value', e.value) break # 结果如下 # 1 1 2 3 5 8 # Generator return value: done