2 理解说明yield与生成器

  • Python中,使用yield的函数被称为生成器函数generator function)。
  • 生成器有两种方法:next()send(),都可以调用生成器
  • yield VS return
    • return作为结尾的普通函数直接返回所有结果,程序终止不再运行,并销毁局部变量;
    • yield会产生一个断点,暂停函数,挂起函数,保存当前状态。并且在yield处返回某个值,返回之后程序就不再往下运行了。
    • yieldreturn他们都在函数中使用,并履行着返回某种结果的职责。
  • yield的函数则返回一个可迭代的生成器generator对象,你可以使用for循环或者调用next()方法send()方法遍历这个产生的实例化生成器对象来提取结果。

2 yield生成迭代器与next()、send()方法

生成器函数返回一个生成器对象,针对生成器对象python提供了两个主要方法,一个是next()一个是send()

2.1 next()函数

第一次对生成器对象调用next(),相当于启动生成器,会从生成器函数的第一行代码开始执行,直到第一次执行完yield语句后,跳出生成器函数

之后调用next(),进入生成器函数后,会从yield语句的下一句语句开始执行,然后重新运行到yield语句,执行后,跳出生成器函数。后面再次调用next(),依此类推。即执行一次next()则调用一次生成器函数,直至抛出不可迭代的错误。

def fun_yield():print("starting fun yield")while True:res = yield 4print("判断yield之后是否继续执行",res)g = fun_yield() # 调用这个函数只是会得到一个生成器
print("函数结果是一个生成器:",g)print("对此生成器还是进行调用:")
print("第一次调用")
print("生成器的返回值",next(g))
print("第二次调用")
print("生成器的返回值",next(g))
print("第三次调用")
print("生成器的返回值",next(g))

  • 程序开始执行以后,因为fun_yield中有yield关键字,所以函数并不会真的执行,而是先得到一个实例化的生成器对象结果1可以看出不会真的运行。
  • 直到调用next()fun_yield正式开始执行,先执行函数中的print("starting fun yield"),然后进入while循环,结果2可以看出
  • 程序遇到yield关键字,然后把yield理解为returnreturn了一个4之后,程序停止,并没有执行后面的print("判断yield之后是否继续执行",res)操作,此时next(g)语句执行完成,所以输出的前两行,接下来准备运行第二次调用,结果3可以看出
  • 执行下面的print("生成器的返回值",next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从上一次yield停止的断点开始执行,也就是要执行print("判断yield之后是否继续执行",res)操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数,也就相当于说res里面是没有内容的),所以这个时候res赋值是None,所以接着下面的输出就是None结果4可以看出
  • 之后的程序会继续在while里执行,又一次碰到yield,这个时候同样return出4,结果5可以看出

说明:

  • 理解yieldreturn与generator的关系和区别:

    • yield的函数是一个生成器函数,直接使用不会真正的运行,而是返回一个实例化的生成器对象,每次通过generator迭代中会从yield处得到返回数据
  • python提供生成器的方法next()next()就相当于“下一步”生成哪个数,这一次的next()开始的地方是接着上一次的yield停止的地方,所以调用next()的时候,生成器并不会将函数从头开始执行,只是接着上一步停止的地方开始,然后遇到yield后,返回表达式,此步就结束。

2.2 send()函数

send()函数和next()函数其实很相似,唯一的区别在于send()函数可以传入值,而next()函数不能传值,可以这样说:

next(f) = f.send(None)

第一次调用时不能使用send()发送一个非None的值,否则会出错的,因为没有yield语句来接收这个值。

这里给出一个例子:

def fun_yield():print("starting fun yield")while True:res = yield 4print("判断yield之后是否继续执行",res)g = fun_yield() # 调用这个函数只是会得到一个生成器
print("函数结果是一个生成器:",g)print("对此生成器还是进行调用:")
print("第一次调用")
print(next(g))
print("生成器的返回值",g.send(1))
print("第二次调用")
print("生成器的返回值",g.send(2))
print("第三次调用")
print("生成器的返回值",g.send(3))

主要变化:

之前res的值是None,现在变成1,2,3,因为send是发送参数给res,因为上面讲到,return的时候,并没有把4赋值给res,下次执行的时候只好继续执行赋值操作,只好赋值为None了。

如果用send的话,出现的情况是,先接着上一次(yield 4之后)执行,先把接受到的1,2,3等赋值给了res,然后执行打印的作用,程序执行再次遇到yield关键字,yield会返回后面的值后,程序再次暂停,直到再次调用next()方法或send()方法。

注意:

生成器在迭代的过程中可以改变当前迭代值,而修改普通迭代器的当前迭代值往往会发生异常,影响程序的执行。(因为生成器send()方法)

def myList(num):      # 定义生成器now = 0           # 当前迭代值,初始为0while now < num:val = (yield now)  # 返回当前迭代值,并接受可能的send发送值;now = now + 1 if val is None else val  # val为None,迭代值自增1,否则重新设定当前迭代值为valmy_list = myList(5)   # 得到一个生成器对象
print my_list.next()  # 返回当前迭代值
print my_list.next()
my_list.send(3)       # 重新设定当前的迭代值
print my_list.next()

3 常用的代码例子

3.1 例子1 输出斐波那契数列的前N个数(比较常用的使用方式)

调用fab(5)不会执行fab函数,而是返回一个generator对象。在for循环执行时,每次循环都会执行fab函数内部的代码,执行到yield b时,fab函数就返回一个迭代值,下次迭代时,代码从yield b的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到yield

def fab(max): n, a, b = 0, 0, 1 while n < max: yield b      # 使用 yield a, b = b, a + b n = n + 1
for n in fab(5): print(n,end = " ")f = fab(5)
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))

3.2 例子2 使用types和inspect库判断是否为生成器或生成器函数(关于类型判断的说明)

def fab(max): n, a, b = 0, 0, 1 while n < max: yield b      # 使用 yield a, b = b, a + b n = n + 1
from inspect import isgeneratorfunction
print(isgeneratorfunction(fab))
print(isgeneratorfunction(fab(5)))import types
print(isinstance(fab, types.GeneratorType) )
print(isinstance(fab(5), types.GeneratorType))

要注意区分fabfab(5)

fab是一个generator function,而fab(5)是调用generator function返回的一个generator,好比类的定义和类的实例的区别,fab是无法迭代的,而fab(5)是可迭代的。

3.3 例子3 多个生成器不影响

每次调用含有yield的函数行成一个新的generator实例,各实例互不影响。

def fab(max): n, a, b = 0, 0, 1 while n < max: yield b      # 使用 yield a, b = b, a + b n = n + 1
from inspect import isgeneratorfunction
f1 = fab(3)
f2 = fab(5)
print("f1:",next(f1))
print("f2:",next(f2))
print("f1:",next(f1))
print("f2:",next(f2))
print("f1:",next(f1))
print("f2:",next(f2))
print("f2:",next(f2))
print("f2:",next(f2))

3.4 例子4 文件读取

如果直接对文件对象调用read()方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过yield,不再需要编写读文件的迭代类,就可以轻松实现文件读取:

def read_file(fpath): BLOCK_SIZE = 1024 with open(fpath, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return

4 yield 优势

Python 2 中的一个特性:内建函数 rangexrange。其中,range 函数返回的是一个列表,而 xrange 返回的是一个迭代器。在 Python 3 中,range 相当于 Python 2 中的 xrange;而 Python 2 中的 range 可以用 list(range()) 来实现。

Python做出如下的优化主要原因就是:如果用List的话,会占用更大的空间。这个时候在python2range(1000)就默认生成一个含有1000个数的list了,所以很占内存。

for n in range(1000):a=n

这个时候可以用yield组合成生成器实现

def foo(num):print("starting...")while num<10:num=num+1yield num
for n in foo(0):print(n,end=" ")

之所以要提供这样的解决方案,是因为在很多时候,只是需要逐个顺序访问容器内的元素。大多数时候,不需要一口气获取容器内所有的元素。比方说,顺序访问容器内的前 5 个元素,可以有两种做法:

  • 获取容器内的所有元素,然后取出前 5 个;
  • 从头开始,逐个迭代容器内的元素,迭代 5 个元素之后停止。

显而易见,如果容器内的元素数量非常多(比如有 10 ** 8 个),或者容器内的元素体积非常大,那么后一种方案能节省巨大的时间、空间开销。

现在假设,有一个函数,其产出(返回值)是一个列表。而若知道,调用者对该函数的返回值,只有逐个迭代这一种方式。那么,如果函数生产列表中的每一个元素都需要耗费非常多的时间,或者生成所有元素需要等待很长时间,则使用 yield 把函数变成一个生成器函数,每次只产生一个元素,就能节省很多开销了。

LAST 参考文献

python中yield的用法详解——最简单,最清晰的解释_mieleizhi0522的博客-CSDN博客_yield

Python yield 使用浅析 | 菜鸟教程

如何理解Python中的yield用法? - 知乎

python的yield和yield from - 华为云

Python 中的黑暗角落(一):理解 yield 关键字 | 始终

python中yield的用法(生成器的讲解)相关推荐

  1. Python中yield简单用法

    Python中yield简单用法 你或许知道带有yield的函数在Python中被称之为generator,那何为 generator? 我们暂时抛开generator,先从一个常见编程题目开始,循序 ...

  2. python中yield的用法

    在 Python 开发中,yield 关键字的使用其实较为频繁,例如大集合的生成,简化代码结构.协程与并发都会用到它. 生成器 如果在一个方法内,包含了 yield 关键字,那么这个函数就是一个「生成 ...

  3. python中yield的用法详解——最简单,最清晰的解释(转载)

    首先,如果你还没有对yield有个初步分认识,那么你先把yield看做"return",这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值, ...

  4. python中 yield 的用法详解——最简单,最清晰的解释(排序节省内存消耗)

    首先我要吐槽一下,看程序的过程中遇见了yield这个关键字,然后百度的时候,发现没有一个能简单的让我懂的,讲起来真TM的都是头头是道,什么参数,什么传递的,还口口声声说自己的教程是最简单的,最浅显易懂 ...

  5. Python中yield的用法详解——最简单,最清晰的解释

    yield关键字的使用 当函数中遇到yield关键字,类似于return代码会直接返回 当函数中使用yield关键字返回,那么在多此next中,从第二次开始,代码会从上一次结束的位置开始运行 首先,如 ...

  6. Python中yield和yield from的用法

    yield 后面接的是 future 对象 调用方 委托生成器 yield from 直接给出循环后的结果 yield from 委托者和子生成器直接通信 yield from 直接处理stopIte ...

  7. python的yield是什么意思,python生成器是怎么使用的 python中yield是什么意思

    python中return和yield怎么用的?两个有什么区别?你从未驯服过她,她只是在爱你的时候收起獠牙. yield yield是用于生成器.什么是生成器,你可以通俗的认为,在一个函数中,使用了y ...

  8. python中with是什么意思,Python中With的用法

    在看Dive Into Python中有关描述文件读写那章节的时候,看到了有关with的用法,查阅下相关资料,记录下来,以备后用. 官方的reference上有关with statement是这样说的 ...

  9. [翻译]Python中yield的解释

    问题: Python中yield关键字的作用是什么?它做了什么? 例如,我想理解以下代码 def node._get_child_candidates(self, distance, min_dist ...

最新文章

  1. 2022-2028年中国光刻机行业深度调研及投资前景预测报告
  2. 干货!MySQL 资源大全
  3. 通信系统中对眼图的理解(二)
  4. Mybatis传入多参问题
  5. c语言音像图书管理系统设计,c语言--图书管理系统
  6. JSON处理1.1:100DaysOfJavaEE8
  7. xenserver6.2 内存leak故障处理
  8. 安装win10系统以及升级win10 home至enterprise版本
  9. C#读写ISO15693协议ICODE2标签源码
  10. RK px30 配置ap6212 wifi bt流程记录
  11. can总线程序讲解_CANOpen系列教程02_理解CAN总线协议
  12. 医院各领域榜单。22个科室、100种常见疾病
  13. Linux下安装MySQL、安装注意事项以及安装问题解决等(以腾讯云服务为主)
  14. 如何查询期刊论文影响力——JCR
  15. 如何查看win10电脑系统盘是哪个盘?
  16. DNS主域名服务器、从域名服务器和缓存域名服务器的构建和原理
  17. 新网站如何提升排名?网站排名提升的优化技巧分享
  18. BYOD -- 企业的困境与力量
  19. 第一章 Web应用程序开发基础
  20. 排列组合c几几怎么用计算机算,排列组合C几几怎么算的?

热门文章

  1. 继承TextView简单画一个尺子
  2. vue + ElementUI(饿了么UI) ---增删改查(用localStorage存储数据+过滤搜索)--demo
  3. eclipse java启动参数设置_[Java教程]eclipse.ini配置eclipse的启动参数
  4. 原生js动态实现添加表格数据并某列求和
  5. 论文编辑与投稿——SCI论文投稿信(Cover Letter)、催稿信、修改稿等的写法及模版
  6. 【考研词汇训练营】Day 8 —— complete,traditional,extra,afford,professional,require
  7. 什么是PV、UV和PR值
  8. 【最新版下载】Android studio教程与问题汇总
  9. 采访华为公司副总裁郭海卫
  10. 中学教师资格证《教育知识与能力》——中学德育原则