Python协程 & 异步编程(asyncio) 笔记

最近看到别人代码里面有这种方式,在编码下载歌词的时候想试试,查了资料,把样例放这里吧,理解了皮毛,照猫画虎。

1.协程的实现

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行。例如:

def func1():
    print(1)
    ...   # 协程介入
    print(2)
    
def func2():
    print(3)
    ...   # 协程介入
    print(4)

func1()
func2()

上述代码是普通的函数定义和执行,按流程分别执行两个函数中的代码,并先后会输出:1、2、3、4。但如果介入协程技术那么就可以实现函数见代码切换执行,最终输入:1、3、2、4 。

在Python3.4+可以通过以下:

  • asyncio,在Python3.4中引入的模块用于编写协程代码;
  • async & awiat,在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。

通过标准库的方式实现:asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

import asyncio

@asyncio.coroutine
def func1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)

@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)

tasks = [
    asyncio.ensure_future( func1() ),
    asyncio.ensure_future( func2() )
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

通过关键字实现方法

async & await 关键字在Python3.5版本中正式引入,代替了asyncio.coroutine 装饰器,基于他编写的协程代码其实就是上一示例的加强版,让代码可以更加简便可读。

import asyncio

async def func1():
    print(1)
    await asyncio.sleep(2)  # 耗时操作  
    print(2)

async def func2():
    print(3)
    await asyncio.sleep(2)   # 耗时操作
    print(4)

tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

2.案例:

2.1 同步实现案例

# requests库仅支持同步的http网络请求
import requests

def download_image(url):
  print("开始下载:",url)
  # 发送网络请求,下载图片
  response = requests.get(url)
  # 图片保存到本地文件
  file_name = url.rsplit('_')[-1]
  with open(file_name, mode='wb') as file_object:
      file_object.write(response.content)
print("下载完成")


if __name__ == '__main__':
  url_list = [
      'https://www.1.jpg',
      'https://www.2.jpg',
      'https://www.3.jpg'
  ]
  for item in url_list:
      download_image(item)

2.2 基于协程的实现

# aiohttp 为支持异步编程的http请求库
import aiohttp
import asyncio

async def fetch(session, url):
  print("发送请求:", url)
  async with session.get(url, verify_ssl=False) as response:
      content = await response.content.read()
      file_name = url.rsplit('_')[-1]
      with open(file_name, mode='wb') as file_object:
          file_object.write(content)

async def main():
  async with aiohttp.ClientSession() as session:
      url_list = [
          'https://www.1.jpg',
          'https://www.2.jpg',
          'https://www.3.jpg'
      ]
      tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
      await asyncio.wait(tasks)


if __name__ == '__main__':
  asyncio.run(main())

2.3 下载歌词的部分代码

# aiohttp 为支持异步编程的http请求库
import aiohttp
import asyncio

async def fetch(session, song):
    song_id = song['id']
    song_name = song['name']
    url = get_song_lyrics_url(song_id)
    
    print("开始下载:", song_id, song_name, url)
    async with session.get(url, verify_ssl=False) as response:
        content = await response.content.read()
        
        json_path = f_path + str(song_id) + "-" + song_name + '.json'
        with open(json_path, mode='wb') as file_object:
            file_object.write(content)
    print(song_name, "下载完成:")
            
async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [asyncio.create_task(fetch(session, song)) for song in songs]    
        await asyncio.wait(tasks)
        
await main()
# if __name__ == '__main__':
#     asyncio.run(main())

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据