zoukankan      html  css  js  c++  java
  • 生成器函数yield和使用yield模拟协程

    先看一个栗子:

    # -*- coding:UTF-8 -*-
    __autor__ = 'zhouli'
    __date__ = '2018/12/6 21:08'
    
    
    # 生成器函数,函数里只要有yield关键字
    
    def gen_func():
        yield 1
    
    
    def func():
        return 1
    
    
    if __name__ == "__main__":
        gen = gen_func()
        re = func()
        pass

    生成器函数这个对象是是什么时候产生的呢?是python编译字节码的时候就产生了,

    既然是生成器对象,那么一定可以使用for循环进行遍历,并且yield可以多次

    def gen_func():
        yield 1
        yield 2
        yield 3
        yield 4

    yield的特性:惰性求值, 延迟求值提供了可能

    斐波那契数列的经典举例:

    def fib(index):
        if index <= 2:
            return 1
        else:
            return fib(index-1) + fib(index-2)
    print(fib(10))

    这样虽然可以做出来,但是没有具体的过程,那改进一下

    def fib2(index):
        relist = []
        n,a,b = 0,0,1
        while n<index:
            relist.append(b)
            a, b = b, a+b
            n += 1
        return relist

    假如说现在index很大,上亿,那内存就有可能不够了。

    def fib2(index):
        n,a,b = 0,0,1
        while n<index:
            yield b
            a, b = b, a+b
            n += 1

    改成这样,内部没有维护一个列表,自然而然就不会消耗内存的

    当然这样可以直接进行for循环了

    那生成器的原理是什么呢?适用于什么场景呢?如何区别于函数呢?

    def foo():
        bar()
    
    
    def bar():
        global frame
        frame = inspect.currentframe()
    
    
    # python.exe会用一个叫做 PyEval_EvalFramEx(c函数)去执行foo函数, 首先会创建一个栈帧(stack frame)
    python一切皆对象,栈帧对象, 字节码对象
    当foo调用子函数 bar, 又会创建一个栈帧
    所有的栈帧都是分配在堆内存上,这就决定了栈帧可以独立于调用者存在

    利用生成器表达式读取大文件:

    有人可能讲了,for line in f.open()

    但是如果只有一行呢?

    f.read(4096)  # 先读4096个字符
    f.read(4096)  # 自动再次读取4096个字符
    # 500G, 特殊 一行
    def myreadlines(f, newline):
        buf = ""
        while True:
            while newline in buf:
                pos = buf.index(newline)
                yield buf[:pos]
                buf = buf[pos + len(newline):]
            chunk = f.read(4096)
    
            if not chunk:
                # 说明已经读到了文件结尾
                yield buf
                break
            buf += chunk
    
    
    with open("input.txt") as f:
        for line in myreadlines(f, "{|}"):
            print(line)

    使用单线程去切换任务

    1,线程是由操作系统切换的,单线程切换一位置我们需要自己去调度任务

    2,不在需要锁,并发性高,如果单线程内切换函数,性能将远高于线程切换,并发性更高

    传统函数调用 过程 A->B->C
    我们需要一个可以暂停的函数,并且可以在适当的时候恢复该函数的继续执行
    出现了协程 -> 有多个入口的函数, 可以暂停的函数, 可以暂停的函数(可以向暂停的地方传入值)
    def gen_func():
        #1. 可以产出值, 2. 可以接收值(调用方传递进来的值)
        html = yield "http://projectsedu.com"
        print(html)
        return "bobby"
    
    #1. throw, close
    
    #1. 生成器不只可以产出值,还可以接收值
    
    
    if __name__ == "__main__":
        gen = gen_func()
        #在调用send发送非none值之前,我们必须启动一次生成器, 方式有两种1. gen.send(None), 2. next(gen)
        url = gen.send(None)
        #download url
        html = "天青色等烟雨"
        print(gen.send(html)) #send方法可以传递值进入生成器内部,同时还可以重启生成器执行到下一个yield位置
        print(gen.send(html))
        #1.启动生成器方式有两种, next(), send

    我们需要的是传入一个网址之后返回的内容在原封不动的传回来

     在调用send之后也会执行下一个yield的结果,并且开始的时候必须使用next()或者使用send()一个空值

     调用gen.close()生成器已经结束了

    gen = gen_func()
    print(next(gen))
    gen.throw(Exception, "download error")
    print(next(gen))
    gen.throw(Exception, "download error")

    这三个操作就可以模拟协程,可以暂停,关闭,发送异常

    yield from语法

    在解释之前先了解一下

    from itertools import chain

    chain函数可以将多个可迭代对象进行一个for循环

    my_list = [1,2,3]
    my_dict = {
        "web1":"http://projectsedu.com",
        "web2":"http://www.imooc.com",
    }
    for value in chain(my_list,my_dict,range(10)):
        print(value)

     yield from 后面加的是一个可迭代的对象

    yield from会自动将对象迭代出来

    def g1(iterable):
        yield iterable
    
    def g2(iterable):
        yield from iterable
    
    for value in g1(range(10)):
        print(value)
    for value in g2(range(10)):
        print(value)
    range(10)
    >>>
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    可以看出这两个的区别

    def g1(gen):
        yield from gen
    
    def main():
        g = g1()
        g.send(None)
    
    #1. main 调用方 g1(委托生成器) gen 子生成器
    #1. yield from会在调用方与子生成器之间建立一个双向通道

     具体用法:

    final_result = {}
    
    def sales_sum(pro_name):
        total = 0
        nums = []
        while True:
            x = yield
            print(pro_name+"销量: ", x)
            if not x:
                break
            total += x
            nums.append(x)
        return total, nums
    
    def middle(key):
        while True:
            final_result[key] = yield from sales_sum(key)
            print(key+"销量统计完成!!.")
    
    def main():
        data_sets = {
            "面膜": [1200, 1500, 3000],
            "手机": [28,55,98,108 ],
            "大衣": [280,560,778,70],
        }
        for key, data_set in data_sets.items():
            print("start key:", key)
            m = middle(key)
            m.send(None) # 预激middle协程
            for value in data_set:
                m.send(value)   # 给协程传递每一组的值
            m.send(None)
        print("final_result:", final_result)
    #
    if __name__ == '__main__':
        main()
    """
    看完代码,我们总结一下关键点:
    
    1. 子生成器生产的值,都是直接传给调用方的;调用方通过.send()发送的值都是直接传递给子生成器的;如果发送的是 None,会调用子生成器的__next__()方法,如果不是 None,会调用子生成器的.send()方法;
    2. 子生成器退出的时候,最后的return EXPR,会触发一个StopIteration(EXPR)异常;
    3. yield from表达式的值,是子生成器终止时,传递给StopIteration异常的第一个参数;
    4. 如果调用的时候出现StopIteration异常,委托生成器会恢复运行,同时其他的异常会向上 "冒泡";
    5. 传入委托生成器的异常里,除了GeneratorExit之外,其他的所有异常全部传递给子生成器的.throw()方法;如果调用.throw()的时候出现了StopIteration异常,那么就恢复委托生成器的运行,其他的异常全部向上 "冒泡";
    6. 如果在委托生成器上调用.close()或传入GeneratorExit异常,会调用子生成器的.close()方法,没有的话就不调用。如果在调用.close()的时候抛出了异常,那么就向上 "冒泡",否则的话委托生成器会抛出GeneratorExit异常。
    
    """
  • 相关阅读:
    挂载硬盘,提示 mount: unknown filesystem type 'LVM2_member'的解决方案
    mongo3.4 配置文件 注意事项
    Rsync 传输不需要输入密码
    Robomongo 0.9.0 连接mongo数据库时,提示连接失败 的解决方案
    linux 安装 mongo
    mysql GTID主从复制(主库在线,添加新丛库)
    计算机网络原理精讲第四章--网络层
    Chrome浏览器商店安装的插件保存到本地
    计算机网络原理精讲第三章--链路层
    计算机网络原理精讲第二章--物理层
  • 原文地址:https://www.cnblogs.com/zhoulixiansen/p/10080183.html
Copyright © 2011-2022 走看看