一、PEP 252 简单生成器 (Python 2.2)
提供一种函数,它可以向其调用者返回一个中间结果("下一个值"), 但是维护这个函数的本地状态,以便该函数可以从它中断的地方再一次恢复
一个斐波拉契数列的例子
def fib():
a, b = 0, 1
while 1:
yield b
a, b = b, a+b
当第一次调用fib()时,它将a设置为0,将b设置为1,然后将b返回给调用者。 调用者看到1.当fib恢复时,从它的角度来看,yield语句与print语句完全相同:在所有本地状态完好无损的情况下,fib继续产生。 a和b然后变成1和1,并且fib循环返回到yield,产生1到它的调用者。 等等。 从fib的角度来看,它只是提供一系列结果,就像通过回调一样。 但是从调用者的角度来看,fib调用是一个可以随意恢复的可迭代对象。 就像在线程方法中一样,这允许以最自然的方式对双方进行编码; 但与线程方法不同,这可以在所有平台上高效完成。 事实上,恢复生成器应该不会比函数调用更昂贵。
- Python生成器是一种Python迭代器[1],但是 一种特别强大的类型。
详述: Yield
yield声明
yield: yield_stmt: "yield" expression_list
yield 语句只能在函数内部使用。包含yield语句的函数称为生成器函数。 生成器函数在所有方面都是普通函数对象,但在代码对象的co_flags成员中设置了新的CO_GENERATOR标志。
当调用生成器函数时,实际参数以通常方式绑定到函数本地形式参数名称,但函数体内没有代码被执行。而是返回一个 生成器-迭代器对象(generator-iterator);这符合迭代器协议,因此特别可以以自然的方式使用for循环。(使用非限定词 generator来引用 generator-function 或者 generator-iterator)。
每次调用generator-iterator的.next()方法时,都会执行generator-function的主体中的代码,直到遇到yield或return语句(见下文),或者直到达到函数体的末尾。
如果遇到yield语句,函数的状态将被冻结,并且expression_list的值将返回给.next()的调用方。通过“冻结”,我们意味着保留了所有本地状态,包括局部变量,指令指针和内部评估堆栈的当前绑定:足够的信息被保存以便下次调用.next()时,该函数可以正确的运行就好像yield声明只是另一个外调用叫一样。
yield语句的限制:
-
限制1: tyr/finally构造的try子句中不允许使用yield语句。困难在于不能保证generator将永远被恢复,因此不能保证finally块将永远被执行,这一点违反了yield语句的最终目的
-
限制2:generator在运行时不能恢复
In [8]: def g():
...: i = me.next()
...: yield i
...:
In [9]: me = g()
In [10]: me.next()
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-10-459e3a97b763> in <module>()
----> 1 me.next()
<ipython-input-8-609387bc52bd> in g()
1 def g():
----> 2 i = me.next()
3 yield i
4
ValueError: generator already executing
详述: Return
生成器函数还可以包含已下形式的返回语句: return
注意: expression_list 不允许在生成器主体的return语句中(当然,他们可能出现在生成器中嵌套的非生成器函数的主体中)。
当遇到return语句时,控制会像任何函数return一样继续执行,执行相应的finally子句(如果存在)。然后会引发StopIteration异常,表示迭代器已耗尽。如果控制流在生成器末尾结束而没有显式返回,则也会引发StopIteration异常。
请注意,对于生成器函数和非生成器函数而言,return意味着"我完成了,并且没有什么有趣的可以返回"
return并不总是等同于触发StopIteration: 区别在于如何处理封闭try / except构造,例如:
In [11]: def f1():
...: try:
...: return
...: except:
...: yield 1
...:
In [12]: list(f1())
Out[12]: []
因为在任何函数中,return都退出,但是:
In [1]: def f2():
...: try:
...: raise StopIteration
...: except:
...: yield 42
...:
In [2]: print(list(f2()))
[42]
因为除了任何例外情况外,StopIteration都被裸机捕获。
详述: 生成器和异常传播
如果一个未处理的异常(包括但不限于StopIteration)由一个生成器函数引发或传递,那么异常将以通常的方式传递给调用者,随后尝试恢复生成器函数 raise StopIteration。 换句话说,未处理的异常终止了生成器的使用寿命。
In [4]: def f():
...: return 1/0
...:
In [5]: def g():
...: yield f()
...: yield 42
...:
In [6]: k = g()
In [7]: k
Out[7]: <generator object g at 0x2101370>
In [8]: k.next()
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-8-b18ad230a5d4> in <module>()
----> 1 k.next()
<ipython-input-5-319895aec53f> in g()
1 def g():
----> 2 yield f()
3 yield 42
4
<ipython-input-4-528d02fe14ab> in f()
1 def f():
----> 2 return 1/0
ZeroDivisionError: integer division or modulo by zero
In [9]: k.next()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-9-b18ad230a5d4> in <module>()
----> 1 k.next()
StopIteration:
可以看到,第一次调用k.next()触发了ZeroDivisionError异常,第二次调用时,发现生成器不能恢复运行
详述: try/except/finally
如前所述,try / finally构造的try子句中不允许yield。 结果是发电机应该非常小心地分配关键资源。 除了子句或者try / except构造的try子句外,对最终子句中出现的yield没有限制:
In [16]: def f():
...: try:
...: yield 1
...: try:
...: yield 2
...: 1/0
...: yield 3
...: except ZeroDivisionError:
...: yield 4
...: yield 5
...: raise
...: except:
...: yield 6
...: yield 7
...: except:
...: yield 8
...: yield 9
...: try:
...: x = 12
...: finally:
...: yield 10
...: yield 11
...:
In [17]: list(f())
Out[17]: [1, 2, 4, 5, 8, 9, 10, 11]
二、总结
生成器是一个返回迭代器的函数,但是只能对其迭代一次。这是因为并没有把所有的值存在内存中,而是在运行时生成值。这种需要时才取值的行为相对于循环列表,是十分节省系统资源的。
1. 使用yield 语句定义生成器
yield 语句仅仅用于定一个生成器函数,并且只在生成器函数的主体中使用。当一个生成器函数被调用时,他将返回一个称为生成器迭代器的迭代器或者是更常见的生成器。通过重复调用生成器的next() 方法来执行生成器主体直到引发异常。 当程序执行到yield语句时,生成器的状态被冻结,并且expression_list 的值返回给next()的调用者。 "冻结"指的是保留所有本地状态,包括局部变量的当前绑定,指令指针和内部评估堆栈:保存足够的信息以便下次next()被调用,该函数可以接着上次保存的状态继续运行,就好像这个yield语句只是另一个外部调用。——翻译自官方文档
2. 生成器特点
- 生成器是一个函数(任何包含yield语句的函数就是生成器)
- yield关键字用法像return一样,但是这个函数返回生成器迭代器的迭代器或者是更常见的生成器
- 通过重复使用使用next()方法来执行yield语句获取相关值。注意: 当我调用生成器函数时,函数中的代码并没有运行,函数仅仅返回生成器对象,当使用for进行迭代或者使用 内置的next()函数 或者使用 生成器的next() 方法时,函数中的主体部分才会运转,直至引发异常。
- 生成器一旦执行到yield语句部分,函数会返回本次的返回值,而生成器函数就会被冻结:即函数停在那点等待被重新唤醒。函数被重新唤醒后就从停止的点开始执行。一旦函数运行并没有碰到yeild语句就认为生成器已经为空了
3. yield语句实例
In [155]: def generators(ranges):
...: item = 0
...: for i in range(ranges):
...: yield item # yield语句返回一个生成器
...: item += i
...:
In [156]: gen = generators(3)
In [157]: gen.next()
Out[157]: 0
In [158]: gen.next()
Out[158]: 0
In [159]: gen.next()
Out[159]: 1
In [160]: gen.next()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-160-b2c61ce5e131> in <module>()
----> 1 gen.next()
StopIteration:
4. 生成器表达式
定义生成器的除了使用yield语句外, 还可以使用生成器表达式
生成器表达式返回的是生成器;生成器定义的方法类似于列表推导式,但是生成器用的是圆括号;list()方法可以把生成器以列表的形式显示出来;
In [165]: gen = (i for i in range(10))
In [166]: gen
Out[166]: <generator object <genexpr> at 0x3d59fa0>
In [167]: gen.next() #使用next()方法执行生成器主体
Out[167]: 0
In [168]: list(gen) # 使用list()函数 显示生成器。注意这里只会产生剩余的元素
Out[168]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
In [169]: list(gen) # 再一次调用就会返回空列表,因为yield 语句已经结束
Out[169]: []
生成器表达式也可以在当前的圆括号内直接使用。比如在函数调用中就不需要增加另外一个圆括号了。
如果需要生成大量的的值再迭代计算,最好不要使用列表推导式,而是使用生成器推导式直接迭代。因为列表推导式会实例化一个列表,从而丧失迭代的优势。
In [50]: sum(i for i in range(10))
Out[50]: 45
5. 生成器表达式和列表推导式的性能对比
In [49]: timeit.timeit("sum(i for i in range(100000))", number=1000)
Out[49]: 4.699758167989785
In [50]: timeit.timeit("sum([i for i in range(100000)])", number=1000)
Out[50]: 5.3653446859971154
通过这个例子可以看出,在大量数据迭代的情况下,生成器表达式比列表推导式的性能好。因为列表推导式实实在在的生成了一个列表,在这里个例子中,先生成列表再求和,已经是2层for循环了,自然就要慢一点了。











网友评论