zoukankan      html  css  js  c++  java
  • Python小白学习之路(二十二)—【生成器】

    一.什么是生成器?

    生成器可以理解成是一种数据类型,特殊地是生成器可以自动实现迭代器协议
    其他的数据类型需要调用自己内置的__iter__方法
    所以换种说法,生成器就是可迭代对象

    !回忆:很重要的迭代器协议

    对象必须提供一个 next 方法,执行该方法要么返回迭代中的下一项,
    要么就引起一个Stoplteration异常,以终止迭代(只能往后走不能往前退)


    二.生成器的分类(两类)

    python中生成器的表现形式
    python中提供生成器的方式


    一类是生成器函数;另一类是生成器表达式

    第一类:关于生成器函数

    • 与常规函数定义相同。但是返回值时使用yield而不是return。
    • yield语句一次返回一个结果,可以进行多次返回(而return只能返回一次)
    • yield每次返回一个结果,在每个结果中间,挂起函数的状态(其实就是记住我函数执行到哪一行了)
    #举例:
    def test ():
        yield 1
    g = test()      #并不会执行test()函数,需要通过 g.__next__()方法来触发生成器函数执行
    print(g) 
    print(g.__next__())
    
    #执行结果
    <generator object test at 0x0051AA70>
    1

    在说生成器表达式之前,补充三元表达式和列表解析

    三元表达式:(顾名思义,就是有三个元素呗)

    以前我们是这么写程序的:

    name = 'alex'
    if name == 'alex':
        print('Ok')
    else:
        print('Not ok')


    利用三元表达式我们是这么写程序的:

    name = 'alex'
    res = 'Ok' if name == 'alex' else 'Not ok'  #三元表达式
    print(res)
    #执行结果
    Ok

    (每一圈为一个元)

    列表解析:

    列表解析式的语法格式为:

    • [i操作 for i in 列表 if 表达式1 and 表达式2]
    • (其实就是用中括号[]将三元表达式框起来)

    举例理解:
    #我要通过程序下10个鸡蛋
    #以前我是这么写的

    egg_list = []
    for i in range(10):
        egg_list.append('鸡蛋%s' %i)
    print(egg_list)
    #执行结果
    ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']

    #通过列表解析式我是这么写程序的

    l0 = [ '鸡蛋%s' %i for i in range(10) ]
    print(l0)
    #执行结果
    ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
    
    l1 = [ '鸡蛋%s' %i for i in range(10) if i < 5 ]
    print(l1)
    #执行结果
    ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4']
    
    l2 = [ '鸡蛋%s' %i for i in range(10) if i < 3 or i > 7]
    print(l2)
    #执行结果
    ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋8', '鸡蛋9']


    总结:列表解析式优缺点
    优点:取值方便(如果列表的长度较小时使用列表解析会很方便,)
    缺点:如果列表的长度很大的时候,使用列表解析会占用很多的内存资源,此时可以使用生成器表达式来节省内存资源

    第二类:关于生成器表达式

    生成器表达式:(就是将列表解析式的中括号变成圆括号)

    #举例:
    l0 = ('鸡蛋%s' %i for i in range(10))
    print(l0)
    print(l0.__next__())
    print(l0.__next__())
    print(l0.__next__())
    
    #执行结果
    <generator object <genexpr> at 0x0045AA70>
    鸡蛋0
    鸡蛋1
    鸡蛋2

    小结:
    1.将列表解析式的 [] 换成 () 得到的就是生成器表达式
    2.列表解析式与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
    3.python使用迭代器协议让 for 循环变得更加通用。大部分内置函数也是使用迭代器协议来访问对象的

    举例:

    sum 函数

    sum(x ** 2 for x in range(4))  #sum 直接按照迭代器协议访问对象(类比for循环)
    sum([x ** 2 for x in range(4)])  #所以并不需要将对象 x ** 2 for x in rang(4) 加上一个中括号变成列表解析式,将所有的值取出来构成一个列表再进行求和运算

    三、通过两段程序代码来感受一下生成器的优势  

    #今天所举得列子不是下蛋就是吃包子(视频课上老师就是这么讲的)
    #我也深深的怀疑
    #为什么老师这么钟爱吃包子和下鸡蛋

    #下蛋程序一:
    def xiadan():
        res = []
        for i in range(10000):
            res.append('鸡蛋%s' %i)
        return res
    print(xiadan())
    
    #缺点一:占用空间较大
    #缺点二:效率低    
    #下蛋程序二:
    def xiadan():
    for i in range(10000):
        yield '鸡蛋%s' %i
    lmj = xiadan()
    print(lmj.__next__())
    #第一段程序是一旦执行 xiadan()这个函数,先下了10000个鸡蛋来占用内存空间,在去执行其他操作
    #第二段程序是通过生成器函数yield来返回我所需要的鸡蛋,我边用(通过__next__() 触发生成器函数)鸡蛋,边下鸡蛋

    以生成器函数为例,对生成器进行总结

    • 语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义

    差别在于生成器使用yield语句进行返回一个值,而常规函数使用return语句返回一个值

    • 自动实现迭代器协议:对于生成器,python会自动实现迭代器协议,以便应用到迭代器背景中

    由于生成器自动实现了迭代器协议,所以我们可以直接调用它的next方法,并且在没有值可以返回的时候生成器自动生成Stoplteration异常

    • 状态挂起:生成器使用yield语句返回一个值。yield 语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行


    优点一:
    生成器的好处就是延迟计算,一次返回一个结果。也就是说它不会一次生成所有的结果,这对于大数据量
    处理非常有用(前面下鸡蛋的例子)

    优点二:
    生成器还能提高代码可读性

    1.使用生成器以后,代码行数更少(在保证代码可读性的前提下,代码行数越少越好)
    2.不适用生成器,对于每次结果,我们首先看到的是result.append(index),其次才是index
    也就是说,我们每次看到的,是一个列表的append操作,只是append的是我们想要的结果。
    使用生成器的时候,直接yield index,少了列表的append操作的干扰,我们一眼能够看出,代码是要进行什么操作。

     

    四、触发生成器执行的三种方式

      • 方式一:__next__()
      • 方式二:next()
      • 方式三:send()
    #举例:
    
    def xiadan():
        for i in range(10000):
        yield '鸡蛋%s' %i
    
    g = xiadan()
    print(g.__next__())
    print(next(g))
    print(g.send(None))
    
    #执行结果
    鸡蛋0
    鸡蛋1
    鸡蛋2

    关于 send() 总结来源于一下文章并且结合自己的理解
    看到一篇关于 yield 总结特别好的文章
    链接:https://www.cnblogs.com/renpingsheng/p/8635777.html

    send()必须传一个参数,可为 None 或者 其他值

    作用:


    1. yield相当于return ,控制的是函数的返回值
    2. x = yield的另外一个特性,接受send传过来的值,赋值给 x

    举例理解:
    
    def test():
        print('开始执行函数')
        first = yield
        print('第一次', first)
        second = yield
        print('第二次', second)
        yield
    g = test()
    print(next(g))
    print(g.send(1))
    print(g.send(2))
    #程序执行过程分析
    # 1.程序开始执行以后,因为test函数中有yield关键字,所以test函数并不会真的执行,而是先得到一个生成器g.
    # 2.直到调用next方法,test函数正式开始执行,先执行test函数中的print方法,打印开始执行函数。然后执行first = yield
    # 3.程序遇到yield关键字,程序暂停,此时next(g)语句执行完成,打印next(g)执行结果,即yield传回的结果,为None
    # 4.程序执行g.send(1),程序会从yield关键字那一行继续向下运行,send会把1这个值传递给yield
    # 5.yield接收到send方法传递过来的值,然后由yield赋值给first变量
    # 6.由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次遇到yield关键字,程序暂停,此时g.send(1)语句执行完成,打印g.send(1)执行结果,即yield传回的结果,为None
    # 7.程序执行g.send(2),程序会从yield关键字那一行继续向下运行,send会把2这个值传递给yield
    # 8.yield接收到send方法传递过来的值,然后由yield赋值给second变量
    # 9.由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次遇到yield关键字,程序暂停,此时g.send(2)语句执行完成,打印g.send(2)执行结果,即yield传回的结果,为None

    写在后面:

    珍爱眼睛  远离电子产品

    从上了研究生阶段  眼睛就开始有虹膜炎

    隔段时间就来打扰我

    不能看电脑不能看手机不能看强光

    还拼命流眼泪

    我也真是佩服自己

    正好这个阶段我就在看书学python  也抽时间看了 许三观卖血记

    文学素养还是要培养的

    我要做祖国新时代的四有新人  有文化 有道德 有。。。还有什么来着

    哈哈

    爱吃火锅的人运气不会太差

    爱吃火锅的人怎么可能轻易放弃



  • 相关阅读:
    UI设计常用网站 火树银花carol
    SQlite3创建数据库
    代码中的奥卡姆剃刀原理
    接口和上传服务器
    npm升级package.json依赖包到最新版本号
    设计无限滚动下拉加载,实践高性能页面真谛
    a标签带参页面跳转并在跳转页面接收参数
    树状数组—区间修改+单点查询 详解
    求逆元的四种方法
    (非线段树)区间修改_单点查询
  • 原文地址:https://www.cnblogs.com/guoruxin/p/10067741.html
Copyright © 2011-2022 走看看