Python yield详解

文章目录

  • Python yield详解
  • 由“斐波那契”深入理解yield案例
    • 第一个版本
    • 第二个版本
      • 问题的引出
    • 第三个版本
    • 第四个版本
    • 总结
      • 细化总结
      • 归纳总结
    • 引深
      • 如何判断一个def是否是一个特殊的 generator 函数?
      • 类的定义和类的实例
      • 另一个例子
  • 另一个角度理解yield案例
    • 理解
    • 示例1
      • 代码
      • 解释
      • 小结
    • 示例2
      • 代码
      • 解释

由“斐波那契”深入理解yield案例

​ 斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到

第一个版本

def fab(max):n, a, b = 0, 0, 1while n < max:print ba, b = b, a + bn = n + 1

执行 fab(5),我们可以得到如下输出:

>>> fab(5)
1
1
2
3
5

第二个版本

提高 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。

def fab(max): n, a, b = 0, 0, 1 L = [] while n < max: L.append(b) a, b = b, a + b n = n + 1 return L

可以使用如下方式打印出 fab 函数返回的 List:

>>> for n in fab(5):
...     print(n)
...
1
1
2
3
5

问题的引出

稍微高级的代码的 fab 函数通过返回 List 能满足复用性的要求,但是更有经验的开发者会指出,该函数在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,最好不要用 List 来保存中间结果,而是通过 iterable 对象来迭代

第三个版本

利用 iterable 我们可以把 fab 函数改写为一个支持 iterable 的 class

class Fab():"""Python3:自定义迭代器的类需要同时实现__iter__和__next__方法。Python2:自定义迭代器的类需要同时实现__iter__和next方法。"""def __init__(self, max):self.max = maxself.n, self.a, self.b = 0, 0, 1def __iter__(self):return selfdef __next__(self):if self.n < self.max:r = self.bself.a, self.b = self.b, self.a + self.bself.n = self.n + 1return rraise StopIteration()for n in Fab(5):print(n)"""也可以使用:Fab类通过 next() 不断返回数列的下一个数,内存占用始终为常数:f = Fab(5)print(next(f))print(next(f))print(next(f))print(next(f))print(next(f))
"""
1
1
2
3
5

​ 然而,使用 class 改写的这个版本,代码远远没有第一版的 fab 函数来得简洁。如果我们想要保持第一版 fab 函数的简洁性,同时又要获得 iterable 的效果,yield 就派上用场了。

第四个版本

​ 第四个版本的 fab 和第一版相比,仅仅把 print b 改为了 yield b,就在保持简洁性的同时获得了 iterable 的效果。

def fab(max):n, a, b = 0, 0, 1while n < max:yield b# print ba, b = b, a + bn = n + 1for n in fab(5):print(n)
1
1
2
3
5

总结

细化总结

​ 简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

​ 也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:

>>> f = fab(5)
>>> f.next()
1
>>> f.next()
1
>>> f.next()
2
>>> f.next()
3
>>> f.next()
5
>>> f.next()
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration

​ 当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。

归纳总结

我们可以得出以下结论:

​ 一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

​ yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。

引深

​ 要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别

如何判断一个def是否是一个特殊的 generator 函数?

可以利用 isgeneratorfunction 判断:

def fab(max):n, a, b = 0, 0, 1while n < max:yield b# print ba, b = b, a + bn = n + 1from inspect import isgeneratorfunction
print(isgeneratorfunction(fab))  # True

类的定义和类的实例

def fab(max):n, a, b = 0, 0, 1while n < max:yield b# print ba, b = b, a + bn = n + 1import types
print(isinstance(fab, types.GeneratorType))  # False
print(isinstance(fab(5), types.GeneratorType))  # True

fab 是无法迭代的,而 fab(5) 是可迭代的

每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:

>>> f1 = fab(3)
>>> f2 = fab(5)
>>> print 'f1:', f1.next()
f1: 1
>>> print 'f2:', f2.next()
f2: 1
>>> print 'f1:', f1.next()
f1: 1
>>> print 'f2:', f2.next()
f2: 1
>>> print 'f1:', f1.next()
f1: 2
>>> print 'f2:', f2.next()
f2: 2
>>> print 'f2:', f2.next()
f2: 3
>>> print 'f2:', f2.next()
f2: 5

另一个例子

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

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

另一个角度理解yield案例

理解

​ 先把yield看做return,这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了。看做return之后再把它看做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器),好了,如果你对这些不明白的话,那先把yield看做return,然后直接看下面的程序,你就会明白yield的全部意思了

示例1

代码

def foo():print("starting...")while True:res = yield 4print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
starting...
4
********************
res: None
4

解释

解释代码运行顺序,相当于代码单步调试:

  1. 程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

  2. 直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

  3. 程序遇到yield关键字,然后把yield想想成return , return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果 , 第二个是return出的结果)是执行print(next(g))的结果,

  4. 程序执行print("*"*20),输出20个*

  5. 又开始执行下面的print(next(g)) , 这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None

  6. 程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4 ,所以不管执行多少次print(next(g)),res都是None

小结

​ 到这里你可能就明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

示例2

代码

def foo():print("starting...")while True:res = yield 4print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(g.send(7))

这个例子就把上面那个例子的最后一行换掉了,输出结果:

starting...
4
********************
res: 7
4

解释

​ 先大致说一下send函数的概念:此时你应该注意到这次res的值变成了7,这是因为,send是发送一个参数给res的,因为上面讲到,return的时候,并没有把4赋值给res,下次执行的时候只好继续执行赋值操作,只好赋值为None了

而如果用send的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了res,然后执行next的作用,遇见下一回的yield,return出结果后结束。

解释代码运行顺序,相当于代码单步调试:

  1. 程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

  2. 直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

  3. 程序遇到yield关键字,然后把yield想想成return , return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果 , 第二个是return出的结果)是执行print(next(g))的结果,

  4. 程序执行print("*"*20),输出20个*

  5. 程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量

  6. 由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次进入while循环

  7. 程序执行再次遇到yield关键字,yield会返回后面的值4后,程序再次暂停,直到再次调用next方法或send方法。

Python yield 详解(嚼碎了喂你,一篇精通,无需再看其他文章)相关推荐

  1. python - yield详解

    文章源自查看博客:python中yield的用法详解--最简单,最清晰的解释_冯爽朗的博客-CSDN博客_python yield 自己整理思路使用,希望大家去看原作者的博客,讲解超级清晰且易理解!! ...

  2. python yield 详解

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

  3. Python保留字详解

    python的保留字并不多,一共33个 ​ ​1.False if q=False:#Flase 2.None 表示该值是一个空对象,空值是Python里一个特殊的值,用None表示.None不能理解 ...

  4. python区块链开发_Fabric区块链Python开发详解

    Hyperledger Fabric是最流行的联盟区块链平台.Fabric区块链Python开发详解课程 涵盖Fabric区块链的核心概念.Fabric网络搭建.Node链码开发.Python应用开发 ...

  5. python装饰器setter_第7.27节 Python案例详解: @property装饰器定义属性访问方法getter、setter、deleter...

    上节详细介绍了利用@property装饰器定义属性的语法,本节通过具体案例来进一步说明. 一.    案例说明 本节的案例是定义Rectangle(长方形)类,为了说明问题,除构造函数外,其他方法都只 ...

  6. 【python】详解类class的继承、__init__初始化、super方法

    原文链接; https://blog.csdn.net/brucewong0516/article/details/79121179?utm_medium=distribute.pc_relevant ...

  7. python与golang_Golang与python线程详解及简单实例

    Golang与python线程详解及简单实例 在GO中,开启15个线程,每个线程把全局变量遍历增加100000次,因此预测结果是 15*100000=1500000. var sum int var ...

  8. python 最小二乘法_最小二乘法及其python实现详解

    最小二乘法Least Square Method,做为分类回归算法的基础,有着悠久的历史(由马里·勒让德于1806年提出).它通过最小化误差的平方和寻找数据的最佳函数匹配.利用最小二乘法可以简便地求得 ...

  9. 【python】详解multiprocessing多进程-Pool进程池模块(二)

    [python]详解multiprocessing多进程-process模块(一) [python]详解multiprocessing多进程-Pool进程池模块(二) [python]详解multip ...

最新文章

  1. 算法分析与设计之多处最优服务次序问题2
  2. Matlab求方差,均值,均方差,协方差的函数
  3. Nginx配置proxy_pass转发的/路径问题
  4. Android Volley完全解析4:带你从源码的角度理解Volley
  5. Abp vNext异常处理的缺陷/改造方案
  6. The Falling Leaves UVA - 699
  7. Android中关于Adapter的使用(下)BaseAdapter
  8. linux中的帮助命令man(manual 手册,帮助,指南)
  9. uni app对接php,thinkphp5 对接手机uni-app的unipush推送(个推)
  10. hbase 查询_不用ES也能海量数据复杂查询秒回
  11. linux下集成开发环境之ECLIPSE--在线调试、编译程序
  12. 如何让您的应用程序进入苹果App Store?(上)
  13. 数据库基础-update语句详解
  14. Ramda 函数库参考教程
  15. 解决VM虚拟机连不上网络的问题
  16. #Paper Reading# Implicit Neural Representations with Periodic Activation Functions
  17. 辨别 利用AAC转成无损格式音乐 的假无损
  18. Tmall 工商图片去水印,同时识别公司名称
  19. 《Android Studio 开发实战从零基础到App上线》笔记1
  20. JAVA EE(jsp)

热门文章

  1. html 显示opencv视频文件夹,openmv与opencv的区别是什么?
  2. Flex 绘制跟随鼠标移动的十字交叉线
  3. matlab基础与常用语法
  4. 多点解读虎扑:获字节巨资加持的体育大咖,为何再次梦碎IPO?
  5. 高中计算机会考作弊,贵州高中信息技术会考考生违纪舞弊处理规定
  6. 调用百度人脸识别API
  7. GB2312汉字大全
  8. 如何从ST官网下载STM32标准外设库
  9. 软件行业排名前100名的企业大全
  10. A股、B股、H股简介