当前位置: 首页 > news >正文

【python】Python中yield的理解与使用|python生成器

2019.05.28推荐这个教程:https://blog.csdn.net/mieleizhi0522/article/details/82142856

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

接下来是正题:

首先,如果你还没有对yield有个初步分认识,那么你先把yield看做“return”,这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了。看做return之后再把它看做一个是生成器(generator)的一部分(带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))

就这么简单的几行代码就让你明白什么是yield,代码的输出这个:

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.

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

****************************************************************************************************************************************

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

再看一个这个生成器的send函数的例子,这个例子就把上面那个例子的最后一行换掉了,输出结果:

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

先大致说一下send函数的概念:此时你应该注意到上面那个的紫色的字,还有上面那个res的值为什么是None,这个变成了7,到底为什么,这是因为,send是发送一个参数给res的,因为上面讲到,return的时候,并没有把4赋值给res,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用send的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了res,然后执行next的作用,遇见下一回的yield,return出结果后结束。

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

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

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

这就结束了,说一下,为什么用这个生成器,是因为如果用List的话,会占用更大的空间,比如说取0,1,2,3,4,5,6............1000

你可能会这样:

    for n in range(1000):
        a=n

这个时候range(1000)就默认生成一个含有1000个数的list了,所以很占内存。

这个时候你可以用刚才的yield组合成生成器进行实现,也可以用xrange(1000)这个生成器实现

yield组合:

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

输出:

    starting...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

 xrange(1000):

    for n in xrange(1000):
        a=n

 其中要注意的是python3时已经没有xrange()了,在python3中,range()就是xrange()了,你可以在python3中查看range()的类型,它已经是个<class 'range'>了,而不是一个list了,毕竟这个是需要优化的。

如果你感觉对你有帮助,你的赞赏是对我最大的支持!
原文:https://blog.csdn.net/mieleizhi0522/article/details/82142856

-------------------------------------------------------------------------------------------------

生成器的两种形式(Python有两种不同的方式提供生成器)-2019-3-19

1.生成器函数:

常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

2.生成器表达式:

类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

https://www.jb51.net/article/134699.htm

//——————————————————————————————————————————————————

1 def consumer():
2     r = 'here'
3     for i in xrange(3):
4         yield r
5         r = '200 OK'+ str(i)

7 c = consumer()
8 n1 = c.next()
9 n2 = c.next()
10 n3 = c.next()

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

然后第二个next调用,进入生成器函数后,从yield语句的下一句语句(第5行)开始执行,然后重新运行到yield语句,执行后,跳出生成器函数,
---------------------
原文:https://blog.csdn.net/pfm685757/article/details/49924099

看到一篇很不错的介绍生成器的文章,但是排版和标点符号让人痛苦,重新编排了一下。

总结:

for 循环中


通常的for...in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。它可以是mylist = [1, 2, 3],也可以是mylist = [x*x for x in range(3)]。


它的缺陷是所有数据都在内存中,如果有海量数据的话将会非常耗内存。生成器是可以迭代的,但只可以读取它一次。因为用的时候才生成。比如 mygenerator = (x*x for x in range(3)),注意这里用到了(),它就不是数组,而上面的例子是[]。

我理解的生成器(generator)能够迭代的关键是它有一个next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。可以用上面的mygenerator测试。

使用next可以很好的看出yield的工作机制。当yield使用完成后,就会报出exception,终止迭代操作。

         带有 yield 的函数不再是一个普通函数,而是一个生成器generator,可用于迭代。工作原理同上,yield在python 里就是一个生成器。当你使用一个yield的时候,对应的函数就是一个生成器了。生成器的功能就是在yield的区域进行迭代处理。

yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回yield后面(右边)的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行。return 的作用:如果没有 return,则默认执行至函数完毕,返回的值 一 般 是 yield的变量。

简要理解:yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始。

带有yield的函数不仅仅只用于for循环中,而且可用于某个函数的参数,只要这个函数的参数允许迭代参数。比如array.extend函数,它的原型是array.extend(iterable)。

send(msg)与next()的区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值。

——换句话说,就是send可以强行修改上一个yield表达式值。比如函数中有一个yield赋值,a = yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用.send(10),那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10

send(msg)与next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数。第一次调用时必须先next()或send(None),否则会报错,send后之所以为None是因为这时候没有上一个yield(根据第8条)。可以认为,next()等同于send(None)。

示例一:


结果:

理解的关键在于:下次迭代时,代码从yield的下一跳语句开始执行。

示例二:

与前面不同的是,这个函数中没有for循环,但它依然可以用于迭代。
node._get_child_candidates函数中有yield,所以它变成了一个迭代器,可以用于迭代。
执行第一次迭代时(其实就是调用next()方法),如果有左节点并且距离满足要求,会执行第一个yield,这时会返回self._leftchild并完成第一个迭代。
执行第二次迭代时,从第一个yield后面开始,如果有右节点并且距离满足要求,会执行第二个yield,这时会返回self._rightchild并完成第一个迭代。
执行第三次迭代时,第二个yield后再无代码,捕获异常,退出迭代。

调用过程:


上面的node._get_child_candidates(self, distance, min_dist, max_dist)是放在extend()函数中作为参数的,为什么可以这么用,就因为extend函数的参数不仅仅支持array,只要它是一个迭代器就可以。它的原型是array.extend(iterable)。


示例三:

实例:

def parse_page_list(self, json_data):
        """解析JSON数据的函数"""
        json_obj = json.loads(json_data)
        if json_obj and 'data' in json_obj.keys():
            data_list = json_obj.get('data')
            # r = [] # 内存中存储的
            for item in data_list:

 # yield: 好处:1.不会将所有数据取出来存入内存中;而是返回了一个对象;可以通过对象获取数据;用多少取多少,可以节省内容空间。
                        #2.除了能返回一个值,还不会终止循环的运行; yield item.get('article_url')
                #

推荐博客文章:http://pyzh.readthedocs.io/en/latest/the-python-yield-keyword-explained.html

                        https://www.jianshu.com/p/d09778f4e055

                        https://jingyan.baidu.com/article/4f7d5712f2fe3b1a2019270c.html
原文:https://blog.csdn.net/qq_33472765/article/details/80839417

相关文章:

  • 【数据结构】八大数据结构分类 研读笔记
  • 【python】list、tuple、dict、set、dataframe、narray、series之间的区别
  • 【python】python3.7数据分析入门学习笔记 研读
  • 【VS输出UTF8】 C++ 控制台程序中输出UTF8字符乱码问题解决方法
  • 【python】python一些热点问题
  • 【thrift】thrift 研究
  • 【人工智能】我的人工智能之旅——线性回归 研读
  • 【python】Python Web服务器并发性能测试
  • 【protocolbuff】linux下安装google protobuf[实践] --未成功
  • 【ML】之 线性回归(实战) 研读
  • 【VS2017】VS2017离线安装教程
  • 【hadoop】为什么要用hadoop?
  • 【容器适配器】什么是容器适配器和用法
  • 【socket】setsockopt函数的作用和说明
  • 【hadoop】Hadoop编程实例之MapReduce
  • 【译】理解JavaScript:new 关键字
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • CentOS 7 修改主机名
  • ComponentOne 2017 V2版本正式发布
  • js学习笔记
  • JS学习笔记——闭包
  • LeetCode刷题——29. Divide Two Integers(Part 1靠自己)
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • PHP面试之三:MySQL数据库
  • Python连接Oracle
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • uni-app项目数字滚动
  • Vim Clutch | 面向脚踏板编程……
  • Vue.js源码(2):初探List Rendering
  • WinRAR存在严重的安全漏洞影响5亿用户
  • 从0实现一个tiny react(三)生命周期
  • 从PHP迁移至Golang - 基础篇
  • 多线程 start 和 run 方法到底有什么区别?
  • 机器学习中为什么要做归一化normalization
  • 2017年360最后一道编程题
  • Prometheus VS InfluxDB
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • 组复制官方翻译九、Group Replication Technical Details
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • #define用法
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • ( 10 )MySQL中的外键
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (三分钟)速览传统边缘检测算子
  • (四) 虚拟摄像头vivi体验
  • (转)大型网站架构演变和知识体系
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • 、写入Shellcode到注册表上线
  • .NET 设计一套高性能的弱事件机制
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .net与java建立WebService再互相调用