美文网首页
大师兄的Python学习笔记(十四): 迭代器、生成器和协程

大师兄的Python学习笔记(十四): 迭代器、生成器和协程

作者: superkmi | 来源:发表于2020-03-24 19:02 被阅读0次

    大师兄的Python学习笔记(十三): 理解装饰器
    大师兄的Python学习笔记(十五): Socket编程

    一、关于迭代器(Iterator)

    1. 可迭代对象
    • 可直接作用于for循环的数据类型就叫可迭代对象(内置__iter__方法)。
    • 可迭代对象分为两类:集合数据类型生成器
    1.1 集合数据类型
    • 比如: dict、str、list、tuple、set等数据类型。
    # dict
    >>>d = dict(a=1,b=2)
    >>>for i in d:
    >>>    print(i)
    a
    b
    
    # list
    >>>l = list([1,2,3,4,5])
    >>>for i in l:
    >>>    print(i)
    1
    2
    3
    4
    5
    
    # string
    >>>s = 'Hello World!'
    >>>for i in s:
    >>>    print(i)
    H
    e
    l
    l
    o
     
    W
    o
    r
    l
    d
    !
    
    # tuple
    >>>t = (1,2,3,4,5)
    >>>for i in t:
    >>>    print(i)
    1
    2
    3
    4
    5
    
    # set
    >>>s = {1,2,3,4,5}
    >>>for i in s:
    >>>    print(i)
    1
    2
    3
    4
    5
    
    1.2 生成器(generator)
    • 生成器是一种边循环边计算的机制。
    • 在函数中使用yield关键字返回值而不是return
    • yield不会像return一样停止程序,而是会将本次循环的代码执行完。
    >>>def gen_workingday():
    >>>    days = ['mon','tue','wed','thu','fri']
    >>>    for d in days:
    >>>        yield d # 每次迭代的代码会储存在这里
    
    >>>for d in gen_workingday(): 
    >>>    print("today is {}".format(d))
    today is mon
    today is tue
    today is wed
    today is thu
    today is fri
    
    1.3 生成器表达式
    • 可以用生成器表达式的方式生成生成器。
    • 生成器表达式的语法与列表推导式类似,只是把[]改成了()
    >>>days = ['mon','tue','wed','thu','fri']
    >>>wd = (d for d in days)
    
    >>>while True:
    >>>    try:
    >>>        print("today is {}".format(next(wd)))
    >>>    except StopIteration:
    >>>        print("End of generator!")
    >>>        break
    today is mon
    today is tue
    today is wed
    today is thu
    today is fri
    End of generator!
    
    2. 迭代器
    • 可以被next()函数调用(或包含__next__方法的)并不断返回下一个值的对象称为迭代器。
    • 如果next()没有下一个值,则抛出异常:StopIteration
    >>>def gen_workingday():
    >>>    days = ['mon','tue','wed','thu','fri']
    >>>    for d in days:
    >>>        yield d
    >>>        print("today is {}".format(d)) # 放在这里还是会被执行
    
    >>>wd = gen_workingday()
    >>>while True:
    >>>    try:
    >>>        next(wd)
    >>>    except StopIteration:
    >>>        print("End of generator!")
    >>>        break
    today is mon
    today is tue
    today is wed
    today is thu
    today is fri
    End of generator!
    
    • 迭代器的好处:省资源。
    • 迭代器并不会事先执行计算结果,而是每次迭代时进行计算。

    二、关于协程(Coroutine)

    • 协程是为非抢占式多任务产生子程序的计算机程序组件。
    • 协程允许不同入口点在不同位置暂停或开始执行程序。
    • 协程只有一个线程。
    • 其实可以理解为生成器的工作机制。
    1. 协程代码的实现
    • yield关键字,与生成器中的功能相似但不同,用法是:<var> = yield,协承被触发时会将值传给<var>并从yield后的代码继续执行。
    • send关键字, 用于协程预激和触发。(类型生成器中的next)
    • 协程在启动时需要用send(None)预激活。
    • 一个简单的例子用来理解协程:
    >>>def cor(): 
    >>>    print("start")
    >>>    x = yield
    >>>    print("预激\n")
    
    >>>    y = yield 1
    >>>    print("第一次yield\n")
      
    >>>    z = yield 2
    >>>    print("第二次yield\n")
     
    >>>if __name__ =="__main__": # 主进程
    >>>    c = cor() 
    >>>    c.send(None) # 预激协程
    >>>    try:
    >>>        a = c.send(1) # 调用协程
    >>>        print("received_a:",a)
    >>>        b = c.send(2)
    >>>        print("received_b:",b)
    >>>        c = c.send(3) # 这里会触发exception,所以后面的print不会被执行
    >>>        print("received_c:",c)
    >>>    except StopIteration as e:
    >>>        print("StopIteration触发",e)
    start
    预激
    
    received_a: 1
    第一次yield
    
    received_b: 2
    第二次yield
    
    StopIteration触发 
    
    • 协程版的生产者-消费者模型:
    >>>def producer(cons):
    >>>    cons.send(None) # 第3步, 预激活
    >
    >>>    for n in range(1,5): # 第7步
    >>>        print("正在生产第{}件产品。".format(n)) # 第8步
    >>>        c = cons.send(n) # 第9步, 触发生成器并返回值
    >>>        print(c) # 第13步
    >>>    cons.close()
    >
    >>>def consumer():
    >>>    r = "" # 第4步
    >>>    while True: # 第5步
    >>>        n = yield r # 第6步, 返回并切换到producer / # 第12步,将r作为值返回
    >>>        if not n: # 第10步
    >>>            return
    >>>        r = "正在消费第{}件产品。".format(n) # 第11步
    >
    >>>if __name__ == "__main__":
    >>>    c = consumer() # 第1步, 构造生成器
    >>>    producer(c) # 第2步, 调用函数
    正在生产第1件产品。
    正在消费第1件产品。
    正在生产第2件产品。
    正在消费第2件产品。
    正在生产第3件产品。
    正在消费第3件产品。
    正在生产第4件产品。
    正在消费第4件产品。
    
    2. 协程的四种状态
    • 可以使用inspect包的getgeneratorstate模块查看协程状态。
    • 协程包含四种状态:
    状态 含义
    GEN_CREATED 等待开始执行
    GEN_RUNNING 解释器正在执行
    GEN_SUSPENDED 在yield表达式处暂停
    GEN_CLOSED 执行结束
    • 把前面的案例加上状态展示:
    >>>from inspect import getgeneratorstate
    
    >>>def cor(): # 简单的协程
    >>>    print("start")
    >>>    print("**状态:{}**".format(getgeneratorstate(c))) # GEN_RUNNING
    >>>    x = yield
    >>>    print("预激\n")
        
    >>>    y = yield 1
    >>>    print("第一次yield\n")
    
    >>>    z = yield 2
    >>>    print("第二次yield\n")
    
    >>>if __name__ =="__main__": # 主进程
    >>>    c = cor() 
    >>>    print("**状态:{}**".format(getgeneratorstate(c))) # GEN_CREATED
    >>>    c.send(None) # 预激协程
    >>>    try:
    >>>        print("**状态:{}**".format(getgeneratorstate(c))) # GEN_SUSPENDED
    >>>        a = c.send(1) # 调用协程
    >>>        print("received_a:",a)
    >>>        b = c.send(2)
    >>>        print("received_b:",b)
    >>>        c = c.send(3) # 这里会触发exception,所以后面的print不会被执行
    >>>        print("received_c:",c)
    >>>    except StopIteration as e:
    >>>        print("StopIteration触发",e)
    >>>        print("**状态:{}**".format(getgeneratorstate(c))) # GEN_CLOSED
    **状态:GEN_CREATED**
    start
    **状态:GEN_RUNNING**
    **状态:GEN_SUSPENDED**
    预激
    
    received_a: 1
    第一次yield
    
    received_b: 2
    第二次yield
    
    StopIteration触发 
    **状态:GEN_CLOSED**
    
    3. 终止协程
    3.1 方法一:通过抛出异常终止

    1)直接抛出异常

    • 通过raise StopIterationgenerator.throw(<exception>)方式直接抛出异常终止协程。
    >>>def cor(): # 简单的协程
    >>>    print("start")
    
    >>>    x = yield
    >>>    print("预激\n")
           
    >>>    y = yield 1
    >>>    print("第一次yield\n")
               
    >>>    z = yield 2
    >>>    print("第二次yield\n")
       
    >>>if __name__ =="__main__": # 主进程
    >>>    c = cor() 
    
    >>>    c.send(None) # 预激协程
    >>>    try:
    
    >>>        a = c.send(1) # 调用协程
    >>>        print("received_a:",a)
           
    >>>        c.throw(StopIteration) # 直接抛出异常
           
    >>>        b = c.send(2)
    >>>        print("received_b:",b)
    >>>        c = c.send(3) 
    >>>        print("received_c:",c)
    >>>    except RuntimeError as e:
    >>>        print("exception触发",e)
    start
    预激
    
    received_a: 1
    exception触发 generator raised StopIteration
    

    2)generator.close()

    • generator.close()方法触发StopIteration异常。
    >>>def cor(): # 简单的协程
    >>>    print("start")
    
    >>>    x = yield
    >>>    print("预激\n")
           
    >>>    y = yield 1
    >>>    print("第一次yield\n")
               
    >>>    z = yield 2
    >>>    print("第二次yield\n")
       
    >>>if __name__ =="__main__": # 主进程
    >>>    c = cor() 
    
    >>>    c.send(None) # 预激协程
    >>>    try:
    
    >>>        a = c.send(1) # 调用协程
    >>>        print("received_a:",a)
           
    >>>        c.close() # 终止协程
           
    >>>        b = c.send(2)
    >>>        print("received_b:",b)
    >>>        c = c.send(3) # 这里会触发exception,所以后面的print不会被执行
    >>>        print("received_c:",c)
    >>>    except StopIteration as e:
    >>>        print("StopIteration触发",e)
    start
    预激
    
    received_a: 1
    StopIteration触发 
    
    3.2 方法二:通过哨符值
    • 通过send()哨符值,判断终止协程。
    • 通常使用None或Ellipsis作为哨符值。
    >>>def cor(): # 子生成器
    >>>    print("start")
        
    >>>    x = yield
    >>>    print("预激\n")
            
    >>>    y = yield 1
    >>>    if y is Ellipsis: # 捕获哨符值,并终止协程,触发StopIteration
    >>>        return
        
    >>>    print("第一次yield\n")
                
    >>>    z = yield 2
    >>>    print("第二次yield\n")
        
    >>>if __name__ =="__main__": # 主进程
    >>>    c = cor() 
    
    >>>    c.send(None) # 预激协程
    >>>    try:
    >>> 
    >>>        a = c.send(1) # 调用协程
    >>>        print("received_a:",a)
            
    >>>        c.send(Ellipsis) # 发送哨符值
           
    >>>        b = c.send(2)
    >>>        print("received_b:",b)
    >>>        c = c.send(3) # 这里会触发exception,所以后面的print不会被执行
    >>>        print("received_c:",c)
    >>>    except StopIteration as e:
    >>>        print("StopIteration触发",e)
    start
    预激
    
    received_a: 1
    StopIteration触发
    
    4. yield from
    • 可以用来建立程序和生成器/协程之间的管道。

    1)创建与生成器之间的双向管道

    • 这里的逻辑是创建了一个生成器和主线程之间的管道,每次使用yield from,会调用一次连接的生成器。
    >>>d = {'a':1,'b':2,'c':3,'d':4,'e':5}
    
    >>>def gen():
    >>>    yield from d
    
    >>>g = gen()
    
    >>>while True:
    >>>    try:
    >>>        print(d[g.send(None)])
    >>>    except StopIteration:
    >>>        print('end of gen.')
    >>>        break
    1
    2
    3
    4
    5
    end of gen.
    

    2)协程的委派生成器

    • 如果理解了上一个案例,就可以理解协程之间的双向管道。
    • 委托生成器只是将主线程yield的内容在主线程和协程中传递。
    • 委托生成器可以让代码更灵活间接,方便处理异常。
    >>># 子协程 
    >>>def average_gen(): 
    >>>    print('cor started...')
    >>>    while True:
    >>>        x = yield
    >>>        if x is None: # 哨兵值
    >>>            break
    >>>        print('recieved:',x)
    >>>    return x
       
    >>># 委托生成器
    >>>def proxy_gen():
    >>>    while True:
    >>>        x = yield from average_gen() # x 只有在yield完全结束才会被赋值
    >>>        if x is None:
    >>>            break
       
    >>>if __name__ == "__main__":
    >>>    gen = proxy_gen()
    >>>    gen.send(None)
    >>>    gen.send(1)
    >>>    gen.send(2)
    >>>    gen.send(3)
    >>>    try:
    >>>        gen.send(None)
    >>>    except StopIteration:
    >>>        print("end of proxy gen.")
    cor started...
    recieved: 1
    recieved: 2
    recieved: 3
    end of proxy gen.
    

    三、asyncio包

    • asyncio包在python标准库中,内置了对异步IO的支持。
    • asyncio本身是一个消息循环(eventloop)。
    • 步骤: 创建消息循环->将协程导入->关闭
    1. @asyncio.coroutine
    • 将生成器标记为coroutine类型,便于导入消息循环。
    2. asyncio.get_event_loop()
    • 创建一个消息循环(eventloop)。
    3. asyncio.sleep(<t>)
    • 本身也是一个协程,可以看成是耗时<t>秒的程序。
    4. EventLoop.run_until_complete(<coroutine>)
    • 将协程抛到EventLoop中。
    >>>import asyncio
    
    >>>@asyncio.coroutine
    >>>def cor():
    >>>    print("start cor...")
    >>>    r = yield from asyncio.sleep(5)
    >>>    print("hello world!")
    
    >>>loop = asyncio.get_event_loop()
    >>>loop.run_until_complete(cor())
    >>>loop.close()
    start cor...
    hello world!
    
    5. asyncio.wait(<tasks>)
    • 将多个协程封装并抛到EventLoop中
    >>>import asyncio
    
    >>>@asyncio.coroutine
    >>>def cor_a():
    >>>    print("start cor_a...")
    >>>    r = yield from asyncio.sleep(2)
    >>>    print("hello world from cor_a!")
    
    >>>@asyncio.coroutine
    >>>def cor_b():
    >>>    print("start cor_b...")
    >>>    r = yield from asyncio.sleep(5)
    >>>    print("hello world from cor_b!")
    
    >>>task = [cor_a(),cor_b()]
    >>>loop = asyncio.get_event_loop()
    >>>loop.run_until_complete(asyncio.wait(task))
    >>>loop.close()
    start cor_a...
    start cor_b...
    hello world from cor_a!
    hello world from cor_b!
    
    6. asyncio.gather(<task1>,<task2>...)
    • 将多个协程抛到EventLoop中。
    >>>import asyncio
    
    >>>@asyncio.coroutine
    >>>def cor_a():
    >>>    print("start cor_a...")
    >>>    r = yield from asyncio.sleep(2)
    >>>    print("hello world from cor_a!")
    
    >>>@asyncio.coroutine
    >>>def cor_b():
    >>>    print("start cor_b...")
    >>>    r = yield from asyncio.sleep(5)
    >>>    print("hello world from cor_b!")
    
    >>>loop = asyncio.get_event_loop()
    >>>loop.run_until_complete(asyncio.gather(cor_a(),cor_b()))
    >>>loop.close()
    start cor_a...
    start cor_b...
    hello world from cor_a!
    hello world from cor_b!
    
    7. asyncio.ensure_future(<cor>)
    • 将协程加入到task中,返回future对象。
    • 按加入的顺序处理。
    >>>import asyncio
    
    >>>@asyncio.coroutine
    >>>def cor_a():
    >>>    print("start cor_a...")
    >>>    r = yield from asyncio.sleep(2)
    >>>    print("hello world from cor_a!")
    
    >>>@asyncio.coroutine
    >>>def cor_b():
    >>>    print("start cor_b...")
    >>>    r = yield from asyncio.sleep(5)
    >>>    print("hello world from cor_b!")
    
    >>>asyncio.ensure_future(cor_a())
    >>>b = asyncio.ensure_future(cor_b())
    >>>loop = asyncio.get_event_loop()
    >>>loop.run_until_complete(b)
    >>>loop.close()
    start cor_a...
    start cor_b...
    hello world from cor_a!
    hello world from cor_b!
    
    8. async/await
    • 新版本中,为了简化代码,可以使用async/await搭配替换代码。
    • async替换@asyncio.coroutine
    • await替换yield from
    >>>import asyncio
    
    >>>async def cor():
    >>>    print("start cor...")
    >>>    r = await asyncio.sleep(5)
    >>>    print("hello world!")
    
    >>>loop = asyncio.get_event_loop()
    >>>loop.run_until_complete(cor())
    >>>loop.close()
    start cor_a...
    start cor_b...
    hello world from cor_a!
    hello world from cor_b!
    
    9. asyncio.open_connection()
    • 用协程处理网络连接数据流。
    • 与request库的用法类似,可以用一个线程处理多个协程的数据流处理。
    >>>import asyncio
    
    >>>async def wget(host):
    >>>    print('wget {}'.format(host))
    >>>    connect = asyncio.open_connection(host,80)
    >>>    reader,writer = await connect
    >>>    header = "get / http/1.0\r\nHost: {}\r\n\r\n".format(host)
    >>>    writer.write(header.encode())
    >>>    await writer.drain()
    >>>    async for line in reader:
    >>>        print("{} header > {}".format(host,line.decode('unicode_escape').rstrip()))
    
    >>>if __name__ == '__main__':
    >>>    hosts = ['www.baidu.com','www.sina.com.cn']
    >>>    wget_host = [wget(host) for host in hosts]
    >>>    loop = asyncio.get_event_loop()
    >>>    tasks = asyncio.wait(wget_host)
    >>>    loop.run_until_complete(tasks)
    >>>    loop.close()
    wget www.baidu.com
    wget www.sina.com.cn
    www.baidu.com header > HTTP/1.1 400 Bad Request
    www.baidu.com header > 
    www.sina.com.cn header > HTTP/1.1 400 Bad Request
    www.sina.com.cn header > Server: nginx
    www.sina.com.cn header > Date: Mon, 23 Mar 2020 12:17:27 GMT
    www.sina.com.cn header > Content-Type: text/html
    www.sina.com.cn header > Content-Length: 150
    www.sina.com.cn header > Connection: close
    www.sina.com.cn header > X-Via-CDN: f=edge,s=cnc.jinan.union.69.nb.sinaedge.com,c=2408:8207:24a6:5651:d49e:2689:d3af:6c65;
    www.sina.com.cn header > 
    www.sina.com.cn header > <html>
    www.sina.com.cn header > <head><title>400 Bad Request</title></head>
    www.sina.com.cn header > <body>
    www.sina.com.cn header > <center><h1>400 Bad Request</h1></center>
    www.sina.com.cn header > <hr><center>nginx</center>
    www.sina.com.cn header > </body>
    www.sina.com.cn header > </html>
    

    四、aiohttp包

    • aiohttp是基于asyncio实现的HTTP框架。
    • 以下案例创建了一个简单的http服务器:
    >>>import asyncio
    >>>from aiohttp import web
    
    >>>async def index(request):
    >>>    await asyncio.sleep(1)
    >>>    return web.Response(body=b'<h1>Index</h1>')
    
    >>>async def hello(request):
    >>>    await asyncio.sleep(1)
    >>>    text = '<h1>hello, %s!</h1>' % request.match._info['name']
    >>>    return web.Response(body=text.encode('utf-8'))
    
    >>>async def start_server(loop):
    >>>    app = web.Application()
    >>>    app.router.add_route('GET','/',index)
    >>>    app.router.add_route('GET','/hello/{name}',hello)
    >>>    srv = await loop.create_server(web.AppRunner(app),'127.0.0.1',12000)
    >>>    print('Server started at http://127.0.0.1:12000...')
    >>>    return srv
    
    >>>if __name__ == '__main__':
    >>>    loop = asyncio.get_event_loop()
    >>>    loop.run_until_complete(start_server(loop))
    >>>    loop.run_forever()
    

    参考资料



    本文作者:大师兄(superkmi)

    相关文章

      网友评论

          本文标题:大师兄的Python学习笔记(十四): 迭代器、生成器和协程

          本文链接:https://www.haomeiwen.com/subject/guonehtx.html