V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
cxa
V2EX  ›  问与答

一个有趣的小例子,带你入门协程模块-asyncio

  •  
  •   cxa · 2018-12-20 16:03:36 +08:00 · 883 次点击
    这是一个创建于 1951 天前的主题,其中的信息可能已经有所发展或是发生改变。

    上篇文章写了关于 yield from 的用法,简单的了解异步模式, 这次让我们通过一个有趣例子带大家了解 asyncio 基本使用。

    目标效果图

    基本原理

    1.通过不停的依次顺序迭代"|/-"中的一个字符。 2.每次输出前使用退格符模拟一个动态效果。所谓的退格符就是删除上一个字符串,并在原来的位置输出新的字符串。 代码实现:

      1 import  itertools
      2 import sys
      3 import time
      4 flush=sys.stdout.flush
      5 for i in  itertools.cycle("|/-\\"):
      6      print('\b'*len(i)+i,end='')
      7      flush()
      8      time.sleep(.1)
    

    代码解释

    1,2,3 行导入需要的包。

    4 行定义 7 行调用,强制刷新缓存。 当我们打印一些字符时,并不是调用 print 函数后就立即打印的。 一般会先将字符送到缓冲区,然后再打印。这就存在一个问题, 如果你想等时间间隔的打印一些字符, 但由于缓冲区没满,不会打印。就需要采取强制刷新等手段了。

    5 行,使用 itertools.cycle 无穷的迭代括号内的字符串。

    6 行,print 默认是 print(end='\n'),这里修改其默认方法 end='',不换行。 关键作用的是'\b','\b'*len(i)表示多次退格,长度由迭代的字符的个数决定。

    8 行 模拟休眠 0.1 秒。 这里只是一个简单的效果演示,下面我们使用一个使用协程的例子。 ###使用 asyncio 完成同样的功能 该例子参考流畅的 python,我对其作了部分修改。先看代码,后面再做解释。

    # -*- coding: utf-8 -*-
    # @Time : 2018/12/19 9:08 PM
    # @Author : cxa
    # @File : 18-2.py
    # @Software: PyCharm
    # 通过协程以动画形式显示文本式旋转指针
    import asyncio
    import itertools
    import sys
    import time
    async def spin(msg):  # (1)
        write, flush = sys.stdout.write, sys.stdout.flush  # (2)
        for char in itertools.cycle('|/-\\'):  #(3) 
            status = char + ' ' + msg
            print(status, end='')
            flush()  #(4) 
            # write('\b' * len(status))  # (5) 
            print('\b' * len(status), end='')  # (6) 
            try:
                await asyncio.sleep(.1)  # (7) 
            except asyncio.CancelledError: # (8) 
                break
        # write(" " * len(status) + '\b' * len(status))  # (9) 
        print(" " * len(status) + '\b' * len(status), end='') # (10)
    
    
    async def slow_function():
        # 假装等待 io 一段时间
        await asyncio.sleep(3)
        return "very good!"
    
    
    async def supervisor():
        # loop = asyncio.get_event_loop() # (11)
        # spinner = loop.create_task(spin('thking!')) # (12)
        spinner = asyncio.ensure_future(spin('thking!'))  # (13)
        print('spinner object:', spinner) # (14)
        result = await slow_function()  # (15)
        spinner.cancel()   # (16)
        return result
    
    
    def main():
        loop = asyncio.get_event_loop()  # (17)
        result = loop.run_until_complete(supervisor())  # (18)
        loop.close()# (19)
        print("Result:", result)
    
    
    if __name__ == '__main__':
        main()
    

    下面对上面编号进行一一讲解。 首先导入必须的包,其中 asyncio 就是我们要使用的协程包。 (1)def 代表一个函数或者方法,如果在前面加 async def 这个就变成协程了。不再是一个方法。 在 python3.4 的时候通过使用 @asyncio.coroutine 来修饰一个函数使其变为一个协程。现在不推荐使用。

    (2) 定义对象方便后面使用。

    (3)itertools.cycle 会把一个可迭代对象无限重复下去。

    (4)强制刷新缓存

    (5)(6)这两个是等价的: 当我们在使用 print 的时候,实际上是调用了 sys.stdout.write(obj+'\n'),print 在打印时会自动加个换行符。 这里就是一开始说的使用指定字符串长度的退格符

    (7)我们使用 asyncio.sleep 函数来模拟 IO 操作。

    (8)执行(16)的时候触发。

    (9)(10)这两个是等价的,输出最后的显示结果。

    (11)(12)这两句可以用(13)来替代使用 asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个 task。

    (14) 输出的是一个协程对象

    (15)使用 await 把控制权交给主循环,以便 loop 调用其他的协程。

    (16)Task 对象可以取消,取消后会在协程当前暂停的 yield 处抛出 asyncio.CancelledError 异常。

    (17)(18) asyncio.get_event_loop 方法可以创建一个事件循环, 然后使用 run_until_complete 将协程注册到事件循环,并启动事件循环。协程的返回值是这次调用的返回值。

    (19)结束循环。

    参考资料:

    流畅的 python 第 16 章

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1552 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 17:03 · PVG 01:03 · LAX 10:03 · JFK 13:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.