Python/生成器
生成器(generator)是一种返回一个值的迭代器,每次从该迭代器取下一个值。
生成器表达式(generator expression)类似列表解析(list comprehension)表达式的语法,只不过把列表解析的[]换成(),但是它返回的是一个生成器对象而不是列表对象。
生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要迭代器的类一样写__iter__()和__next__()方法了,只需要一个yield关键字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种惰性加载的模式生成值。
使用生成器表达式
[编辑]# 生成器表达式 my_generator = (x for x in range(10))
# 遍历生成器 for item in my_generator: print(item)
基于yield的生成器
[编辑]Python 2.2的PEP 255开始引入生成器。函数如果包含yield指令,该函数调用的返回值是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。yield可以暂停一个函数并返回此时的中间结果。该函数将保存执行环境并在下一次恢复。
生成器中,如果没有return语句,则执行到函数完毕时将返回StopIteration异常。如果在执行过程中遇到return语句,则直接抛出StopIteration异常,终止迭代。如果在return后返回一个值,那么这个值作为StopIteration异常的说明,不是程序的返回值。
可用close()手动关闭生成器函数,后面再调用生成器会直接返回StopIteration异常。
使用 yield 关键字创建生成器函数。生成器函数使用 yield 关键字来生成值,每次调用生成器的 __next__() 方法时,生成器会从上一次停止的地方继续执行。
# 生成器函数
def my_generator():
for x in range(10):
yield x
# 遍历生成器
for item in my_generator():
print(item)
生成器函数最大的特点是可以接受通过send()从外部传入的一个值,并根据该值计算结果后返回。这是生成器函数最难理解的地方,也是最重要的地方,协程的实现依赖于此。语法为
receive=yield value
其中receive将收到send方法中的参数的值。在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。
def cumulative_sum():
total = 0
while True:
value = (yield total)
if value is None:
break
total += value
gen = cumulative_sum()
print(next(gen)) # 输出: 0,启动生成器并得到初始总和
print(gen.send(10)) # 输出: 10,发送10,总和变为10
print(gen.send(20)) # 输出: 30,发送20,总和变为30
print(gen.send(5)) # 输出: 35,发送5,总和变为35
gen.close() # 关闭生成器
在使用 send() 方法之前,必须先启动生成器,这通常通过调用 next(gen) 或 gen.send(None) 来实现。生成器初始启动时,send() 方法只能传递 None,因为生成器必须首先运行到 yield 表达式处才会暂停等待。
第一次执行之外的每次执行,首先是在上一次停止的yield之处把传入值赋给yield语句赋值号左侧的变量(如果有的话),然后执行到下一个yield语句并返回其右侧表达式(如果有的话)的值。
throw()用来向生成器函数送入一个异常。生成器内部可以捕捉、处理此异常,也可以不处理此异常。
def my_generator():
try:
while True:
yield
except Exception as e:
print("Error:", e)
# 使用 throw() 方法向生成器抛出异常
my_generator = my_generator()
next(my_generator)
my_generator.throw(ValueError("Error "))
基于yield from的生成器
[编辑]Python3.3版本的PEP 380中添加了yield from语法,允许一个生成器将其部分操作委派给另一个生成器。
基于yield的生成器的局限性在于只能向它的直接调用者yield值。例如:
def g(x):
yield 1
yield 2
def foo():
iterator = g(0)
print(next(iterator))
if __name__ == '__main__':
next(foo()) # 错误,只能直接调用生成器
这意味着那些包含yield的代码不能想其他代码那样被分离出来放到一个单独的函数中,让外界去调用这个函数。这也正是yield from要解决的。yield from为调用者和子生成器之间提供了一个透明的双向通道,包括从子生成器获取数据以及向子生成器传送数据。
虽然yield from主要设计用来向子生成器委派操作任务,但yield from可以向任意的迭代器委派操作。
yield from iterable本质上等于for item in iterable: yield item的缩写版。例如:
def g(x):
yield from range(x, 0, -1)
yield from range(x)
list(g(5)) #结果为[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
委托生成器的作用:在调用方和子生成器之间行成双向通道。双向通道的含义:调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。只有子生成器结束(return)了,yield from左边的变量才会被赋值,委托生成器后面的代码才会执行。
不同于普通的循环,yield from允许子生成器直接从调用者接收其发送的信息或者抛出调用时遇到的异常,并且返回给委派生产器一个值。如果对子生成器的调用产生StopIteration异常,委派生成器恢复继续执行yield from后面的语句;若子生成器产生其他任何异常,则都传递给委派生成器。如果GeneratorExit异常被抛给委派生成器,或者委派生成产器的close()方法被调用,如果子生成器有close()的话也将被调用。当子生成器结束并抛出异常时,yield from表达式的值是其StopIteration异常中的第一个参数。例如:
def accumulate(): # 子生成器,将传进的非None值累加,传进的值若为None,则返回累加结果
tally = 0
while 1:
next1 = yield
if next1 is None:
return tally
tally += next1
def gather_tallies(tallies): # 外部生成器,将累加操作任务委托给子生成器
while 1:
tally = yield from accumulate()
tallies.append(tally)
tallies = []
acc = gather_tallies(tallies)
next(acc) # 使累加生成器准备好接收传入值
for i in range(4):
acc.send(i)
acc.send(None)
for i in range(5):
acc.send(i)
acc.send(None)
print(tallies) #输出[6,10]