python代码,如何正确使用生成器里的yield关键字操作?
发布于 作者:苏南大叔 来源:程序如此灵动~生成器在很多高级语言里面都存在,使用方式也都差不多。本文聚焦于python
大蟒蛇中的生成器generator
中的yield
关键字。那么,generator
和普通的函数有何区别呢?yield
和return
又有什么区别呢?这就是苏南大叔在本文中要讨论的内容。
苏南大叔的“程序如此灵动”技术博客,记录苏南大叔的代码感想感悟。测试环境:win10
,python@3.11.0
。
概述
概述一下generator
和yield
,其实和大家已经熟知的function
和return
的作用非常类似。区别就在于:
generator
是使用了yield
关键字的function
。generator
获取数据是使用next(g)
函数,它的数据并不是一次性获得的。而是通过next(g)
一步一步获得的。yield
执行的时候,仅仅返回的是一个数。下次next(g)
的时候,代码将从上一次yield
的后一条语句继续执行。- 一个
generator
里面可以有多个yield
,多个yield
的数据返回规则也是可以不同的。
代码一,最简单方式【next(g)】
在下面的代码中,
- 要明白
rang(n)
的范围是[0,n),代码传入参数2的话,只会涉及0
和1
两个数。 - 因为只涉及两个数,同时也只有一个
yield
,所以next(g)
也只能执行两次,以返回对应的结果。
测试代码如下:
def generator_sn(n):
for i in range(n): # [0,n)
print('内部输出:生成之前...')
yield i+1
print('内部输出:生成之后...')
g = generator_sn(2)
result = next(g)
print("yield返回:",result)
print("外部输出:第一次生成")
result = next(g)
print("yield返回:",result)
print("外部输出:第二次生成")
输出:
内部输出:生成之前...
yield返回: 1
外部输出:第一次生成
内部输出:生成之后...
内部输出:生成之前...
yield返回: 2
外部输出:第二次生成
代码一后续:异常控制
如果仔细查看上一个代码的输出的话,还差一个“内部输出:生成之后...”并没有被输出。但是如果继续next(g)
的话,因为并没有数据可以被yield
了,所以会得到一个异常信息StopIteration
。
这里继续上一个代码的输出,第三次执行next(g)
:
try:
print("try 第三次生成 之前...")
result = next(g) # 这里就发生异常了,后续并不执行,但是意外带出了yield语句之后未被执行的部分。
print(result)
print("try 第三次生成 之后...")
except StopIteration as e:
print("发生异常 StopIteration exception")
输出:
try 第三次生成 之前...
内部输出:生成之后...
发生异常 StopIteration exception
可以看到:yield
语句之后未被执行的部分,在这次失败的next(g)
中被输出了。异常的捕获方式,可以参考文章:
代码二:正确的使用方式【for in循环】
def generator_sn(n):
for i in range(n): # [0,n)
print('内部输出:生成之前...')
yield i+1
print('内部输出:生成之后...')
g = generator_sn(2)
for result in g:
print(result)
print("#############\r\n")
输出:
内部输出:生成之前...
1
#############
内部输出:生成之后...
内部输出:生成之前...
2
#############
内部输出:生成之后...
全部该输出的内容,都被输出了,并且没有任何异常信息。这个【for】使用方式相比较而已,更靠谱一点。但是却脱离了“生成器”迭代的初衷了。
代码三:多重yield
这个情况下,yield
就比return
似乎先进多了,可以多次yield
,并且数据生成规则还能保持不同。测试代码:
def generator_sn(n):
for i in range(n):
print('第一个yield准备...')
yield i
print('第二个yield准备...')
yield i + 10
print('双重yield完成...')
g = generator_sn(2)
for result in g:
print(result)
print("#############\r\n")
输出:
第一个yield准备...
0
#############
第二个yield准备...
10
#############
双重yield完成...
第一个yield准备...
1
#############
第二个yield准备...
11
#############
双重yield完成...
与普通函数对比
def s():
for i in range(10):
yield i * i
def n():
for i in range(10):
return i * i
ss = s()
nn = n()
sss = next(ss)
print(type(s),type(ss),type(sss))
print(type(n),type(nn))
输出:
<class 'function'> <class 'generator'> <class 'int'>
<class 'function'> <class 'int'>
在代码中,并没有显式的出现生成器generator
的字样,它和普通的函数是一样使用def
进行定义的。只有当代码中出现yield
字样的时候,才能把这个函数看成一个生成器。其返回值类型是:<class 'generator'>
。
相关文章
- https://newsn.net/say/python-init-call.html
- https://newsn.net/say/python-def.html
- https://newsn.net/say/python-decorators.html
- https://newsn.net/say/python-decorators-2.html
- https://newsn.net/say/python-decorators-3.html
结论
从代码中可以看到:
yield
是返回数据的标志性代码,但是只是暂停代码,并不会中断代码的执行。return
会中断并返回。next(g)
就是推动yield
流程进行下一步的代码,next(g)
一次,就执行到下一个yield
处。- 多个
yield
就可以返回不同规则的数据。 next(g)
控制不好的话,就会引发异常。但是for result in g
这种方式,就不会引发异常。
结束语
在目前很多高级编程语言里面,都有yield
的身影,是单个函数分次返回多个数值的比较好的节省内存的方式。更多python
相关经验文章,请参考苏南大叔的博客:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。