[Python] 关键字yield和return究竟有什么不同?

学习Scrapy的过程中碰到 yeild 这个关键字,我使用Python快半年了,还真的是第一次遇到这个关键字,于是我花了点时间研究后,终于明白它的作用了,怕下次看到时忘记,所以用这篇文将yield这个关键字重点整理一下。

1. yield的核心目的:为了节省记忆体

如果想要印出0~100的平方时,我们可能会这样写。

powers = [x**2 for x in range(100)]for x in powers:    print(x)

但这样有一个致命问题在于,必须把整个list都存放在记忆体中,100个元素可能还不成问题,但如果今天的对象是一百万笔资料,记忆体可能会承受不了,程式就崩溃了。

接下来就会说明yield要如何节省记忆体,但在此之前,先来谈谈Python的生成器(generator)。

2. 什么是生成器(generator)?

生成器是一个可迭代的物件,可以放在for迴圈的in前面,或者使用next()函数呼叫执行下一次迭代。

和列表的差别在于,生成器会保存上次纪录,并只有在呼叫下一层迭代的时候才载入记忆体执行。

所以将上面的例子改写成生成器,结果是一样的,却可以防止超过记忆体,注意我用的是 ( 而不是 [

powers = (x**2 for x in range(100))for x in powers:    print(x)

3. 函数加入yield后不再是一般的函数,而被视作为生成器(generator)

呼叫函数后,回传的并非数值,而是函数的生成器物件。

4. yield和return一样会回传值,不过yield会记住上次执行的位置

yield和return一样都会回传值并中断在目前位置,但最大不同在于yield在下次迭代时会从上次迭代的下一行接续执行,一直执行到下一个yield出现,如果没有下一个yield则结束这个生成器。而且接续上一个迭代前的变数不会改变,就是维持上次结束前的模样。

这部分我们来看下面这个例子:

def yield_test(n):    print("start n =", n)    for i in range(n):        yield i*i        print("i =", i)    print("end")tests = yield_test(5)for test in tests:    print("test =", test)    print("--------")

执行结果:

start n = 5test = 0--------i = 0test = 1--------i = 1test = 4--------i = 2test = 9--------i = 3test = 16--------i = 4end
从第10、11行看到呼叫yield_test()后回传的不是一个数值,而是一个可迭代的生成器。在第一次迭代时,印出了 "start n = 5",因为不在迴圈中,所以仅仅印出这一次。进入迴圈中,第一次时 i=0,接着遇到yield并回传 0*0 = 0,并回到主程序。主程序的test接收到回传的0,于是印出 "test = 0" 并印出 "--------",结束这次迭代。接着进行第二次迭代,会从上次结束的下一行开始,因此印出 "i = 0"。完成后又回到迴圈开始,这时 i=1,接着再次遇到yield并回传 1*1 = 1,并回到主程序。主程序的test接收到回传的1,于是印出 "test = 1" 并印出 "--------",结束这次迭代。其他次迭代依此类推,直到i=5跳出迴圈,印出 "end" 之后已经没有yield了,生成器会返回一个error StopIteration (这边没有印出来),告诉主程序迭代已经结束了。结束主程序。

看完上面例子后,应该会从原本朦朦胧胧到有点概念了吧,其实yield有点像侦错模式的中断点,只是多了中断时回传值而已。

5. next()呼叫下一次迭代,send(n)呼叫下一次迭代并传递参数

def test():    print("start...")    while True:        throw = yield 10        print("throw:", throw)p = test()print(next(p))print("-----------")print(next(p))print("-----------")print(g.send(7))print("-----------")

执行结果:

start...10-----------throw: None10-----------throw: 710-----------
建立一个可迭代生成器p。next()执行第一次迭代,印出 "start..." 并回传 10,但注意throw在赋予值之前就被中断了。next()执行第二次迭代,因为throw并没有被没有被赋予值,所以印出 "throw: None",接着回传 10。send()传入7,等同于在上次结束的位置填入7,因此 throw=7,印出 "throw: 7"。

顺带一提,第一次迭代不可以send任何数值进去,因为没有上一个位置可以接收。

6. Python range小知识

在Python 2.X中,有分range和xrange两种,range就像第一个例子,生成一个[0, 1, 2, ...]的list。xrange则像第二种例子,使用生成器减少记忆体消耗。

但在Python 3.X后range就等于xrange,使用type()检查会知道已经是range型态了。

print(type(range(10)))   # <class 'range'>

如果开始学就是Python3.X,就不必在意这些细节,继续放心地用range吧!

参考

Python 里的 yield - 让你简单、快速了解 yield 的概念python中yield的用法详解--最简单,最清晰的解释_mieleizhi0522的博客-CSDN博客_python yield彻底理解Python中的yield

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章