生成器也是一种迭代器,但是你只能对其迭代一次。这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。
生成器保存的是算法,每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration
的异常。当然,这种不断调用 next() 实在是太变态了,正确的方法是使用 for 循环,因为生成器也是可迭代对象。所以,我们创建了一个生成器后,基本上永远不会调用 next() ,而是通过 for 循环来迭代它,并且不需要关心 StopIteration 异常。
generator = 函数 + yield
简单说,就是一个函数,里面用到了关键字 yield,就成为了一个生成器
1 | for x in range(5)] g = [x*x |
看起来除了把 []
换成 ()
外没什么不同。但是,你不可以再次使用 for i in g,因为生成器只能被迭代一次:先计算出 0,然后继续计算 1,然后计算 4,一个跟一个的。
yield 关键字
yield
是一个类似 return
的关键字,只是这个函数返回的是个生成器。
1 | def gen(): |
你必须要理解:当你调用这个函数的时候,函数内部的代码并不立马执行,这个函数只是返回一个生成器对象,这有点蹊跷不是吗。
那么,函数内的代码什么时候执行呢?当你使用 for 进行迭代的时候.
第一次迭代中你的函数会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的返回值。
然后,再次执行时从 yield 的下一跳语句开始执行,然后再次遇到 yield 后,再返回那个值,直到没有可以返回的。
如果生成器内部没有定义 yield 关键字,那么这个生成器被认为成空的。这种情况可能因为是循环进行没了,或者是没有满足 if/else 条件。
在上面的例子,我们在循环过程中不断调用 yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成 generator 后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代:
1 | for i in mygen: |
生成器和函数的区别
- 直接调用生成器,不会执行;
- 举个栗子:a = f() # 这里 f() 是个生成器
- 运行上面这句,f() 不会执行,首次执行需要使用 next(a) 或 a.send(None),后面会细讲
- 每次执行,会暂时中断在 yield 关键字处,而且通过 yield 可以返回一个参数
- 下次再接着执行,会从上次中断的 yield 处接着执行,并可以通过 send() 传递参数,当然继续中断在下一个 yield 处
- 如果通过 send() 或 next() 执行 generator,而没有找到下一个 yield,会报错
next() 和 send()
return = send(msg)
- 传递参数 msg 给
当前中断
yield 前面的变量 - 同时返回下一个 yield 后面的参数给 return
- 传递参数 msg 给
return = next(a)
- 没有传递参数或者说传递参数 None 给当前中断 yield 前面的变量
- 同时返回下一个 yield 后面的参数给 return
1 | # 生成器 |
运行结果:
1 | start |
总结
生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
生成器不仅记住了它数据状态,还记住了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
- 节约内存
- 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的