• Python学习笔记:生成器(Generator)详解



    一、列表生成式:可以动态生成列表,而不是用固定值给列表赋值,这样程序会更灵活
    def test(i):
        # 取偶数
        if i % 2 == 0:
            return i
    
    # 普通的生成式
    list1 = [i * 2 for i in range(10)]
    print(list1)
    # 通过一个函数选择值
    list1 = [test(i) for i in range(10)]
    print(list1)

    二、生成器:列表在调用之前已经准备好了,如果这个列表的数据量巨大,那么就一直占用内存并且效率低,
    比如有一个100万元素的列表,本次循环只循环到前面100个,后面的元素没有用到,有没有一种
    方式可以在调用的时候再生成元素呢?这就是生成器。

    # (i * 2 for i in range(10000)) 返回一个生成器的内存地址
    list3 = (i * 2 for i in range(10000))
    注意:生成器不能用“list[0]”这种方式调用里面的元素,因为此时元素还没有生成,只能用for循环来访问
    # 可以用__next()__方法来取得下一个,取得下一个的同时前面一个元素就消失了,所以生成器只记录当前位置的元素。不能往后退,往前用__next()__
    print(list3.__next__())
    print(list3.__next__())
    print(list3.__next__())
    # 一般都是用循环来访问,不会用到__next__()方法
    # 这种方式就是使用了python内置的迭代器,里面内置有next方法
    for i in list3
      print(i)
    举个例子,将斐波那契数列用生成器来生成,(斐波那契数列定义:除了第一个和第二个数,后面的数都是前面两个数字想加得到)
    下面普通写法
    list_num = (i for i in range(10))
    list_new_num = []
    
    for i in list_num:
        if i != 0 and i != 1:
            list_new_num.append(list_new_num[i - 1] + list_new_num[i - 2])
        else:
            list_new_num.append(i)
        print(list_new_num[i])
    斐波那契数列的另外一种写法
    n, a, b = 0, 0, 1  # 连续赋值
    while n < 10:
        print(b)
        a, b = b, a + b
        n = n + 1
    将上面的代码改一下。
    通过yield关键词保存函数的中断状态,外面调用的代码可以先执行,不需要等到所有值都计算出来了再执行,这样可以大大提高效率
    执行到next()的时候再返回yield的位置继续执行函数内部的代码。
    def fib(max):
        n, a, b = 0, 0, 1
    
        while n < max:
            yield b  # 加上yield就变成一个生成器了,相当于保存了b变量此处的值。这个b是要传到外面的,所以用yield标记
            a,b = b,a+b
            n = n+1
        return "出错了"  # 这是错误信息,将会被异常捕获。
    
    # 返回一个生成器,所以fib相当于这个生成器的具体实现。
    g = fib(6)
    
    while True:
        try:
            # 这个next是一个内置方法,作用跟__next()__一样,取出生成器的下一个元素
            # next后进入上面fib函数的yield的位置
            x = next(g)
            print("g:",x)
        except StopIteration as e:  # 这里捕捉异常来结束程序
            print("error:",e.value)
            break
    yield 还可以用做单线程下的协程的调用,虽然仍然是单线程(线程和协程将在后面单独介绍),但是通过yield能大大提高效率
    以下是一个典型的生产消费模型,一个人生产,若干人消费,而且是同时进行,也是利用了yield的特性,我们称为“并行生成器”
    import time
    def consumer(name):
        print("%s准备吃包子了!"%name)
        while True:
            baozi = yield # yield无返回值,为什么赋值给baozhi呢?
    
            print("包子[%s]来了,被[%s]吃了。" % (baozi,name))
    
    # c = consumer("tangwei")
    # c.__next__() # 第一个__next__执行到consumer函数中的yield的位置,然后返回
    # c.__next__() # 第二个__next__继续执行剩下的部分一直到循环继续来执行到yield跳出
    
    # c.__next__() # 第一个__next__执行到consumer函数中的yield的位置,然后返回
    # c.send("肉馅") # send将一个值传递给consumer函数内的yield的位置,也就是赋值给变量baozi然后next,仅调用__next__不会给yield传递值
    
    def producer(name):
        # 生成1个生成器
        c1 = consumer("tangwei")
        # 生成1个生成器
        c2 = consumer("chenyadan")
        # c1准备吃包子,__next__执行到consumer函数中的yield的位置,然后返回
        c1.__next__()
        # C2同上
        c2.__next__()
        print("我是厨师[%s],开始做包子了..."%(name))
        for i in range(10):
            time.sleep(1)
            print("做了一个包子,一人一半..")
            c1.send(i) # send将一个值传递给consumer函数内的yield的位置,也就是赋值给变量baozi,仅调用__next__不会给yield传递值
            c2.send(i)
    
    # 虽然程序依然是顺序执行的,但是因为程序流可以在函数之间跳转,就无需等到一个函数结束以后再执行另外一个函数,这样大大提高了执行效率。
    # 这是单线程下的并行效果,也就是协程,协程是比线程更小的单位,寄生在线程中。
    producer("狗不理")
  • 相关阅读:
    js秒换成天时分
    vuex
    匹配iPhoneX
    ASP 解析json
    WPF 实现多语言支持
    c# 用DotNetZip来解压/压缩文件
    WPF UI Close button
    VB 获取所有窗体菜单信息
    ASP/ASP.NET/VB6文件上传
    c# 上传图片到一个外链相册服务器
  • 原文地址:https://www.cnblogs.com/tangwei-fuzhou/p/12669069.html
Copyright © 2020-2023  润新知