一文搞懂 Python 中的 yield

共 6051字,需浏览 13分钟

 ·

2021-04-08 23:04

↑↑↑关注后"星标"简说Python

人人都可以简单入门Python、爬虫、数据分析
 简说Python推荐 
作者 |somenzz

来源 | Python七号

yield 可以实现生成器,可以实现协程。什么是生成器,什么是协程,如果还不了解,可以继续往下看,概念可以不懂,只要理解它的作用和效果也是可以的。
先翻一下英文单词 yield 的意思:
to produce or provide sth, for example a profit, result or crop
中文意思:出产(作物);产生(收益、效益等);提供。

yield 实现生成器

初学 Python 之时,我遇到 yield 关键字时,就把它理解为一种特殊的 return,能看懂就行,自己也很少用,也就没有深入研究过。直到现在,我需要处理大量数据,数据的大小甚至超过了电脑的可用内存,此时我想起来 yield。比如,我操作 db2 数据库查询数据,当数据的结果很大时,我不想一下子读入内存,我就使用了 yield 关键字返回一行数据,程序处理完后,再取下一行:
def read(self, sql, params=()):
stmt = ibm_db.prepare(self.connection, sql) for index, param in enumerate(params): ibm_db.bind_param(stmt, index + 1, param) ibm_db.execute(stmt) row = ibm_db.fetch_tuple(stmt) while row: yield row row = ibm_db.fetch_tuple(stmt)
可以这么来理解关键字 yield 的用法:它返回了一个值,但程序并未退出,下一次从 yield 后面的代码继续运行,直到后面没有代码,结束运行。这里我们举一个简单的例子看下效果:
>>> def iter_fun():... print("a")... yield 1... print("b")... yield 2... print("c")... yield 3...>>> iter_fun()<generator object iter_fun at 0x107e372a0>>>> for i in iter_fun():... print(i)...a1b2c3>>> x = iter_fun()>>> x.__next__()a1>>> x.__next__()b2>>> x.__next__()c3>>> x.__next__()Traceback (most recent call last): File "<stdin>", line 1, in <module>StopIteration
通过上面的例子,我们可以发现,yield 关键子自动为我们生成来私有方法 __next__ ,这样不会将所有数据取出来存入内存中,用多少取多少,可以节省内存空间。
除此之外,yield 在数据量较大时,执行速度也会提升:
In [14]: def normal_fun(num): ...: result = [] ...: for i in range(0,num): ...: if i%2 == 0: ...: result.append(i) ...: return result ...:
In [14]: def iter_fun(num): ...: for i in range(0,num): ...: if i %2 == 0: ...: yield i ...:
In [15]: %time for i in iter_fun(1000000): a = iCPU times: user 97 ms, sys: 2.55 ms, total: 99.6 msWall time: 97.2 ms
In [16]: %time for i in normal_fun(1000000): a = iCPU times: user 115 ms, sys: 13.6 ms, total: 129 msWall time: 128 ms
In [17]: %time for i in normal_fun(100000000): a = iCPU times: user 10.8 s, sys: 668 ms, total: 11.4 sWall time: 11.4 s
In [18]: %time for i in iter_fun(100000000): a = iCPU times: user 9.1 s, sys: 6.49 ms, total: 9.11 sWall time: 9.12 s
上述代码在 Ipython 终端中执行,可以看出使用 yield 的函数,执行的速度更快。
yield 是自己实现一个生成器最便捷的方式。而 Python 语言的生成器是最有用的特性之一,也是使用不广泛的特性,我曾问过周围用 java 的朋友有没有类似的特性,答曰没有,网上搜了下,确实主流的编程语言都没有,因此 Python 的生成器特性没有引起其他语言转 Python 的工程师的关注。
为什么说生成器非常有用呢?
当你需要处理的数据大小超过你电脑的可用内存时,生成器的懒加载(用的时才读入内存)就非常有效。
比如读文件时,如果你使用下面的方式,文件特别大的话,可能内存一下子就满了:
with open("file.txt","r") as reader: for line in reader.readlines(): #do something pass
推荐的做法是使用文件本身的生成器,这样读多大的文件,也不会撑爆内存:
with open("file.txt","r") as reader: for line in reader: #do something pass
其实我们可以看一下 reader 到底有哪些方法:
>>> with open("/etc/passwd","r") as reader:... dir(reader)...['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines']>>>
我们可以看到,这里面有一个 __iter__ 方法和一个 __next__ 方法,这两个方法是生成器的标志。
想深入学习生成器,迭代器,可迭代对象,可以看我以前的推文:
python 基础系列--可迭代对象、迭代器与生成器
深入理解迭代器和生成器

yield 可以实现协程

yield 关键字还可以实现协程,虽然现在有来 asyncio 和 await 关键字来方便的实现协程,在此之前,实现协程就靠的是 yield。
yield 有一个 send 方法,可以改变 yield 的返回值,是这样用的,先看代码:
In [20]: def fun(): ...: print("a") ...: a = yield 1 ...: print("b") ...: print("a = ",a) ...: b = yield a ...: print("c") ...: print("b = ",b) ...:
In [21]: x = fun()
In [22]: x.__next__()aOut[22]: 1
In [23]: x.send(4)ba = 4Out[23]: 4
In [24]: x.send(5)cb = 5---------------------------------------------------------------------------StopIteration Traceback (most recent call last)<ipython-input-24-9183f5e81876> in <module>----> 1 x.send(5)
第一次执行 x 的 __next__ 方法时,函数执行到第一个 yield 处,打印了 a 返回了值 1,此时变量 a 并未获取到 yield 的返回值,a 为 None ,当执行 x.send(4) 时,a 才获取到值 4,程序运行到第二个 yield 处,后续过程也是一样。
利用这一特性,我们可以和被调用的函数通信,进而可以实现一个生产者消费者模型,代码如下:
import time
def consume(): r = '' while True: n = yield r if not n: return print('[consumer] consuming %s...' % n) time.sleep(1) r = 'well received'
def produce(c): next(c) n = 0 while n < 5: n = n + 1 print('[producer] producing %s...' % n) r = c.send(n) print('[producer] consumer return: %s' % r) c.close()
if __name__=='__main__': c = consume() produce(c)
运行结果如下:
[producer] producing 1...[consumer] consuming 1...[producer] consumer return: well received[producer] producing 2...[consumer] consuming 2...[producer] consumer return: well received[producer] producing 3...[consumer] consuming 3...[producer] consumer return: well received[producer] producing 4...[consumer] consuming 4...[producer] consumer return: well received[producer] producing 5...[consumer] consuming 5...[producer] consumer return: well received
produce 和 consume 函数在一个线程内执行,通过调用 send 方法和yield 互相切换,实现协程的功能。
-END-
最后给大家分享《100本Python电子书》,包括Python编程技巧、数据分析、爬虫、Web开发、机器学习、深度学习。
现在免费分享出来,有需要的读者可以下载学习,在下面的公众号「程序员狮子里回复关键字Python,就行

扫下方二维码添加我的私人微信,可以在我的朋友圈获取最新的Python学习资料,以及近期推文中的源码或者其他资源,另外不定期开放学习交流群,以及朋友圈福利(送书、红包、学习资源等)。

扫码查看我朋友圈

获取最新学习资源

学习更多:
整理了我开始分享学习笔记到现在超过250篇优质文章,涵盖数据分析、爬虫、机器学习等方面,别再说不知道该从哪开始,实战哪里找了

点赞”传统美德不能丢 

浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报