带有 yield 的函数是一种特殊函数,其返回的函数值是一个 generator(生成器)。
yield 示例
举例说明 yield 的用法:生成一个自增平方序列:
def square(n):
for i in range(n):
yield i*i # 将 i*i 放入到返回值的生成器序列中
if __name__=="__main__":
gen = square(5)
print(type(gen)) # 打印出 gen 的类型
while True:
print(gen.__next__())
结果输出:
<class 'generator'>
0
1
4
9
16
Traceback (most recent call last):
StopIteration
可以看出,虽然 square 这个函数没有 return
关键字,但实际上 square(5)
是一个生成器,里面数据的顺序是按照函数中 yield
的顺序来存放的。生成器能够迭代的关键是它有一个 __next__()
方法,工作原理就是通过重复调用这个方法,直到捕获一个异常。
区别:python3 中的生成器的迭代函数为
g.__next__()
或next(g)
,python2 为g.next()
。
yield原理解释
可以将 yield
理解为特殊的 return
,只不过可以 yield
很多次,然后使用 __next__()
方法来读取 yield
过的这些数据。
如果一直调用 __next__
方法,当执行到没有可迭代的值后,程序就会报错:StopIteration
,所以一般不会手动的调用__next__
方法,而是使用for循环:
for i in gen:
print(i)
生成器迭代一次(通过 next
或 for
等)遇到 yield
时就返回 yield
后面的值。
重点是:下一次迭代时,从上一次迭代遇到的 yield
后面的代码开始执行。比如我们多加几个输出语句:
def square(n):
for i in range(n):
print(f'this is {i}')
yield i*i # 将 i*i 放入到返回值的生成器序列中
print(f'result is {i*i}')
if __name__=="__main__":
gen = square(5)
count = 0
for i in gen:
print(f'result:{i}')
print(f'count:{count}\n')
count += 1
结果为:
this is 0
result:0
count:0
result is 0
this is 1
result:1
count:1
result is 1
this is 2
result:4
count:2
result is 4
this is 3
result:9
count:3
result is 9
this is 4
result:16
count:4
result is 16
可以看到,第一次迭代和最后一次迭代的输出结果比较特殊。
第一次迭代时,生成器开始按照 square
内部代码运行,直到遇到 yield
为止,然后退出 square
函数,且返回 0
。
中间的几次迭代,生成器按照 square
内部代码运行,但是是从上一次 yield
的 后一行 即 print(f'result is {i*i}')
这一行开始执行,直到遇到 yield
为止。
最后一次只输出了 result is 16
,这是为什么呢?
这是因为,最后一次迭代时,还是从 print(f'result is {i*i}')
开始执行,但是尝试返回到循环的开头时,发现已经不满足循环条件,因此直接退出,也不会再有 yield
的结果。
yield 中 return 的作用
既然带 yield
的函数不需要 return
,那么如果强制加上 return
会如何?
这时,return
会导致提前结束生成器的迭代。
比如在程序多加一行 return
:
def square(n):
for i in range(n):
print(f'this is {i}')
yield i*i
print(f'result is {i*i}')
return i # 多加一行return
if __name__=="__main__":
gen = square(5)
count = 0
for i in gen:
print(f'result:{i}')
print(f'count:{count}\n')
count += 1
结果为:
this is 0
result:0
count:0
result is 0
第一次迭代是正常的,第二次迭代时,从 print(f'result is {i*i}')
开始运行,但是下一行 return
了,因此生成器认为整个迭代已经结束。这时候如果再调用 __next__()
,就会报错:StopIteration
。
区别:python3 中 yield 函数内 return + 某个值,作用与单独的 return 相同;但是 python2 只允许单独的 return,如果后面加某个值,会报错:SyntaxError: ‘return’ with argument inside generator
备注
- 如一个函数中出现多个
yield
则__next__()
会停止在下一个yield
前。 - 使用生成器的好处:实时生成数据,不全存在内存中。
- 使用生成器的坏处:只可以读取它一次,因为用的时候才生成。