Python中关键词yield详解
今天系统看了一下yield的相关知识,作此文章来记录一下。
首先要学习yield的概念就要知道生成器的概念,生成器是一种可以简单有效的创建迭代器的工具。它们像常规函数一样撰写,但是在需要返回数据时使用yield语句。每当对它调用next(),生成器从它上次停止的地方重新开始(它会记住所有的数据值和上次执行的语句)。
可迭代对象
下面我们来看两个打印结果完全相同的代码:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist :
... print(i)
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator :
... print(i)
这两组代码的打印结果完全相同但是另有不同。对于第一种,mylist就是一个列表,占用一个列表的存储空间,之后还可以再次循环遍历;对于第二种,它实时生成数据,不全存在内存中,不过之后就不能再使用for i in mylist了。
yield关键字
如果你对yield的用法完全不了解,可以先把他当成是一个return
只不过下次运行时,代码会在yield处继续运行。我们来看几个例子就明白了。
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
你觉得它的打印结果会是什么呢?先想一想再往下看吧。
starting...
4
********************
res: None
4
ok,结果就像上图一样,我们慢慢来分析。
在第一个print(next(g))时,next相当于让迭代器往下走一步,初始值是4,结果自然是4.接着打印20个*号做标记。在之后又运行了一次next(g),那么这次就会在上次foo()函数中停止的地方运行,这时候res是没有值的,所以打印出res:None,再之后循环又到了yield时“return”了出来,print(next(g))的print打印出了4.
再来看一个例子:
>>> def echo(value=None):
... print("Begin...")
... try:
... while True:
... try:
... value = (yield value)
... except Exception as e:
... value = e
... finally:
... print("Clean up!!!")
...
>>> generator = echo(1)
>>> print(next(generator))
Begin...
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam')
>>> generator.close()
Clean up!!!
这张图应该可以让你理解代码运行的步骤,这里不做过多解释了。当然也可以自己在IDE里实践观察一下。
最后再来看一个多个yield的例子吧:
def genter():
a = 4
b = 5
c = 6
for i in range(5):
yield a
print('a was print at ' + str(i))
yield b
print('b was print at ' + str(i))
yield c
res = genter()
for i, c in enumerate(res):
print('this is '+str(i)+' steps')
if i > 1:
break
print('qq', c)
结果如下图:
this is 0 steps
qq 4
a was print at 0
this is 1 steps
qq 5
b was print at 0
this is 2 steps
运行的结果表明每次迭代停止在yield之后。你也可以修改代码进一步观察来理解。
最后我们再来说说为什么要用yield。
当数据非常多时,显然生成器比列表这种东西合适得多。
后面再说一些关于yield的一些杂项小知识。
1.
send(msg)与next()的区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值。——换句话说,就是send可以强行修改上一个yield表达式值。比如函数中有一个yield赋值,a
= yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用.send(10),那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10
2.带有 yield 的函数不再是一个普通函数,而是一个生成器generator,可用于迭代。
3.判断一个函数是否是一个特殊的generator函数可以利用isgeneratorfunction()判断。
4.要区分fab和fab(5),前者是generator function,后者是调用fab返回的一个generator,好比类的定义和类的实例的区别。
5.在for循环中相当于自动进行了next。
结尾
本文到这里就结束了,如果你有什么好的想法欢迎在评论里和我讨论,不对的地方我会修改。如果你觉得这篇文章对你有一点点的帮助的话记得点个赞再走哦~
感谢以下文章对我的帮助!:
https://www.runoob.com/w3cnote/python-yield-used-analysis.html
https://blog.csdn.net/mieleizhi0522/article/details/82142856
https://www.php.cn/python-tutorials-424969.html
https://www.zhihu.com/question/345210030/answer/841903171
https://www.jianshu.com/p/b635c48d63c5
有兴趣可以继续查看。