生成器是一个可以快速创建迭代器的工具,结合上文python魔术方法-迭代器进行讲解。
官方文档:https://docs.python.org/3/reference/expressions.html#yieldexpr
语法
上面文章提到了通过__iter__
和__next__
创建迭代器的情况,其实python提供了一个很简单的表达式来创建一个迭代器,它就是yield
表达式。
使用这个表达式,我们可以像编写一般的函数一样来编写,只不过在需要返回数据的时候使用yield
表达式即可。
比如:
def MyRange(end):
start = 0
while start < end:
yield start
start += 1
from collections.abc import *
a = MyRange(2)
print(isinstance(a, Iterator))
print(isinstance(a, Iterable))
for i in a:
print(i)
结果是:
True
True
0
1
可以看见,函数MyRange
里面使用了yield
语句,结果变成了Iterator
,我们在使用for
语句的时候,执行步骤如下:
- start = 0, while 0 < 2,遇到了
yield
语句,返回start的值0,然后print
这值; - 第二次for的时候,执行yield后面的语句,start = 1,while 1<2,遇到了
yield
语句,返回start的值1,然后print
; - 第三次for的时候,执行yield后面的语句,start = 2,while 2<2不成立,此时函数结束运行,会抛出
StopIterator
的异常(这个是自动抛出的,不需要显示的调用raise
语句,可以使用下面的next
方法来查看这个异常)。
def MyRange(end):
start = 0
while start < end:
yield start
start += 1
from collections.abc import *
a = MyRange(2)
print(isinstance(a, Iterator))
print(isinstance(a, Iterable))
print(next(a))
print(next(a))
print(next(a)) # 这里在上一步已经结束,会抛出异常
send和throw函数
我们知道,yield
表达式只能在生成器函数中使用(Yield expressions and statements are only used when defining a generator function, and are only used in the body of the generator function.
),当我们调用这个生成器函数的时候,它返回的是一个迭代器叫做生成器。
send
函数是生成器对象的一个方法,接收一个参数,这个参数是yield
表达式的返回值,send
函数返回生成器的下一个值或者抛出StopIteration
异常(当已经没有下一个值得时候)。
注意上面说的yield
表达式的返回值,我们以前使用的都是yield start
这种格式,其实yield
是有返回值的,默认情况下都是None
,我们修改一下上面的MyRange
def MyRange(end):
start = 0
while start < end:
x = yield start # 这里增加了获取返回值
print(x) # 打印出来
start += 1
m = MyRange(5)
print(next(m))
print(next(m))
结果是:
0
None
1
yield
执行顺序上面已经说明了,这里打印了一个None
,就是yield
的返回值,那么说了这么多,就是为了说明send
函数的参数,会作为yield
的返回值的。
def MyRange(end):
start = 0
while start < end:
x = yield start
print(x)
start += 1
m = MyRange(5)
print(next(m))
print(m.send(10))
打印结果是:
0
10
1
我们在来看看throw
这个函数,它是让yield
产生一个异常,接收三个参数,异常类型,异常值,trackback对象,其中后面两个可选,同send
一样,会返回下一个值。
如果这个异常不在生成器函数里面进行处理,会抛出到调用者
def MyRange(end):
start = 0
while start < end:
try:
x = yield start
except Exception as e:
print(e)
start += 1
m = MyRange(5)
print(next(m))
print(m.throw(Exception, 'Some Exception'))
结果是:
0
Some Exception
1
由于我们在MyRange
里面使用try
捕获了异常,所以这个异常没有继续抛出来,我们的程序可以正常执行,如果不进行捕获,那么我们的程序是不能继续正常执行的。
yield from
格式类似yield from
,其中expression部分可选,就是使用expression部分作为一个子迭代器。
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]
生成器表达式
类似于列表的推导式,只是把[]
替换为了()
,生成器表达式结构更经凑,但是功能不及yield
,比如:
a = (_ for _ in range(2))
上例中,a
就是一个生成器。