基于async和await关键字的协程可以实现异步编程,这也是目前Python异步相关的主流技术。
(一)asyncio模块
- asyncio模块是Python中实现异步的一个模块,该模块在Python3.4的时候发布
- asycnio和await关键字在Python3.5引入
(二)事件循环
- 事件循环就是可以把它当作一个while循环,这个while循环在循环生命周期内运行并执行一些任务,在特定的条件下结束循环
- 获取和创建事件循环的代码:
import asyncio
loop = asyncio.get_event_loop()
(三)协程函数和协程对象
(1)什么是协程函数
- 使用asycn声明的函数就是协程函数
# 使用 async 声明的函数就是协程函数
async def fn():
pass
(2)什么是协程对象
- 协程对象就是协程对象就是调用协程函数之后返回的对象
# 使用 async 声明的函数就是协程函数
async def fn():
pass
# 调用携程函数得到的对象就是协程对象
res = fn()
print(res) # <coroutine object fn at 0x1029684a0>
- 注意事项:
- 调用协程函数时,函数内部的代码不会执行,只是会返回一个协程对象!
(四)协程函数的应用
(1)基本应用
- 第一种
import asyncio
# 声明一个协程函数
async def func():
print("协程函数内部的代码")
def main1():
# 调用协程函数,返回一个协程对象
res=func()
# 创建一个事件循环
loop=asyncio.get_event_loop()
# 将协程对象当作任务提交到事件循环列表中去,协程执行完毕后终止
loop.run_until_complete(res)
if __name__ == '__main__':
main1()
# 协程函数内部的代码
- 第二种
import asyncio
# 声明一个协程函数
async def func():
print("协程函数内部的代码")
def main1():
# 调用协程函数,返回一个协程对象
res=func()
# 执行线程对象内部的代码
asyncio.run(res)
if __name__ == '__main__':
main1()
# 协程函数内部的代码
- 步骤:
- 将协程函数当作任务添加到事件循环的任务列表中
- 然后事件循环检测列表中的写产生是否已经就绪
- 准备就绪就执行协程函数内部的代码
(2)await关键字
- await 是一个只能在协程函数中使用的关键字,用于当协程函数遇到IO操作的时候,挂起当前协程(任务)
- 当前协程任务被挂起后,事件循环可以去执行其他的协程任务
- 当前协程IO处理完成是,可以再次切换回来执行await之后的代码
(1)实例1
# 导入模块
import asyncio
# 定义协程函数
async def func():
print("这是协程内部的代码!!!")
# 模拟IO阻塞
# 遇到IO阻塞后,当前协程被挂起,切换到其他的协程去执行# 当IO结束后,切换到当前协程任务
# 当前的协程被挂起后,事件循环到其他的协程去执行
res=await asyncio.sleep(3)
# IO阻塞返回的结果
print(res)
print("阻塞完毕后的代码!!!")
def main():
# 调用协程函数,返回一个协程对象
res=func()
# 执行协程函数
asyncio.run(res)
if __name__ == '__main__':
main()
# 这是协程内部的代码!!!
# None
# 阻塞完毕后的代码!!!
(2)实例2
# 导入模块
import asyncio
# 定义一个协程函数
async def func():
print("这是协程内部的代码!!!")
# 模拟阻塞
await asyncio.sleep(2)
print("协程内部代码结束!!!")
return "返回值"
async def func1():
print("协程函数func1---里面的代码")
# 遇到IO操作之后被挂起当前的协程任务,等IO操作之后再去继续执行
# 当协程被挂起时,事件循环回去执行其他的协程任务
res= await func()
print(f"IO请求结束,结果为{res}")
def main():
# 调用协程函数,得到协程函数对象
res=func1()
# 执行协程函数
asyncio.run(res)
if __name__ == '__main__':
main()
# 协程函数func1---里面的代码
# 这是协程内部的代码!!!
# 协程内部代码结束!!!
# IO请求结束,结果为返回值
(3)实例3
import asyncio
async def other_tasks():
print('start')
await asyncio.sleep(2) # 模拟遇到了IO操作
print('end')
return '返回值'
async def fn():
print('协程函数内部的代码')
# 遇到IO操作之后挂起当前协程(任务),等IO操作完成之后再继续往下执行。
# 当前协程挂起时,事件循环可以去执行其他协程(任务)
respnse1 = await other_tasks()
print(f'IO请求结束,结果为:{respnse1}')
respnse2 = await other_tasks()
print(f'IO请求结束,结果为:{respnse2}')
def main():
# 调用协程函数,返回一个协程对象
cor_obj = fn()
# 执行协程函数
asyncio.run(cor_obj)
if __name__ == '__main__':
main()
'''
运行结果:
协程函数内部的代码
start
end
IO请求结束,结果为:返回值
start
end
IO请求结束,结果为:返回值
'''
(4)小结
- 上述的实例中之创建了一个任务
- 事件循环的任务列表中也只有一个任务
- 无法演示在遇到IO时,切换到其他任务的效果
- 在程序中要想创建多个任务对象
- 需要用到Task对象来实现
(3)Task对象
- Task用于并发调度协程
- 通过asyncio.create_task(协程对象)的方式创建Task对象
- 这样除了可以让协程加入事件循环中等待被调度执行
- 除了使用asyncio.create_task()函数之外
- 还可以用底层级的loop.create_task()或ensure_future()函数,并且不建议手动实例化Task对象
- 本质上是将协程对象封装成Task对象
- 并将协程立即加入事件循环,同时追踪协程的状态
- 注意事项:
- asyncio.create_task() 函数在 Python3.7 中被加入。
- 在 Python3.7 之前,可以改用低层级的
- asyncio.ensure_future() 函数。
(1)协程运行方式一
# 导入模块
import asyncio
# 定义协程函数
async def func():
print("这是协程函数内部的代码!!!")
# 模拟io
await asyncio.sleep(2)
print("结束!!!")
return '返回值'
async def func1():
print("这是func1函数内部的代码!!!")
# 创建协程,将协程封装到一个Task对象中并立即添加到事件循环列表中去,等待事件循环去执行
task1=asyncio.create_task(func())
# 创建协程,将协程封装到一个Task对象中并立即添加到事件循环列表中去,等待事件循环去执行
task2=asyncio.create_task(func())
# 当执行协程遇到IO后,会自动切换到其他的任务去
# 此出的await是等待相对应得协程全部执行完毕后获取得结果
res1=await task1
res2=await task2
def main():
# 调用协程对象,返回一个协程对象
res=func1()
asyncio.run(res)
if __name__ == '__main__':
main()
# 这是func1函数内部的代码!!!
# 这是协程函数内部的代码!!!
# 这是协程函数内部的代码!!!
# 结束!!!
# 结束!!!
(2)协程运行方式二
# 导入模块
import asyncio
# 定义协程函数
async def func():
print("这是协程函数func的内部代码")
# 模拟io阻塞
await asyncio.sleep(2)
print("协程函数func的颞部代码结束")
return "返回值"
async def func1():
print("func1开始!!!")
# 创建协程,并将协程封装成一个task对象,立即添加到事件循环列表中去,等待事件循环去执行
task_list=[asyncio.create_task(func()),asyncio.create_task(func())]
print("func1结束")
# 当执行某些从遇到IO操作时,会自动切换执行其他的任务
# 此出的await时等待所以的协程执行完毕,并将所有协程的返回值保存到done
# 如果设置了timeout值,则意味着最多等待的秒,完成的协程返回值写入到done,未完成的则写道pending
done,pending=await asyncio.wait(task_list,timeout=None)
print(f"done---{done}")
print(f"pending---{pending}")
def main():
asyncio.run(func1())
if __name__ == '__main__':
main()
# func1开始!!!
# func1结束
# 这是协程函数func的内部代码
# 这是协程函数func的内部代码
# 协程函数func的颞部代码结束
# 协程函数func的颞部代码结束
# done---{<Task finished name='Task-2' coro=<func() done, defined at D:\old boy\python\python28基础\day42\协程.py:221> result='返回值'>, <Task finished name='Task-3' coro=<func() done, defined at D:\old boy\python\python28基础\day42\协程.py:221> result='返回值'>}
# pending---set()
(3)获取返回值
- gather 获取协程的返回值
import asyncio
async def other_tasks():
print('start')
await asyncio.sleep(2) # 模拟遇到了IO操作
print('end')
return '返回值'
async def fn():
print('fn开始')
# 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
task_lis = [
asyncio.create_task(other_tasks()),
asyncio.create_task(other_tasks()),
]
print('fn结束')
# 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
# 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done
# 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。
await asyncio.wait(task_lis, timeout=None)
response = await asyncio.gather(task_lis[0], task_lis[1]) # 将task_lis作为参数传入gather,等异步任务都结束后返回结果列表
print(f'response :>>>> {response}')
def main():
asyncio.run(fn())
if __name__ == '__main__':
main()
'''
fn开始
fn结束
start
start
end
end
response :>>>> ['返回值', '返回值']
'''
(4)aiohtpp对象
-
我们之前学习过爬虫最重要的模块requests,但它是阻塞式的发起请求,每次请求发起后需阻塞等待其返回响应,不能做其他的事情。
- 本文要介绍的aiohttp可以理解成是和requests对应Python异步网络请求库,它是基于 asyncio 的异步模块,可用于实现异步爬虫,优点就是更快于 requests 的同步爬虫。
- 安装方式,pip install aiohttp。
-
aiohttp是一个为Python提供异步HTTP 客户端/服务端编程,基于
asyncio
的异步库。
- asyncio可以实现单线程并发IO操作,其实现了TCP、UDP、SSL等协议,
- aiohttp就是基于asyncio实现的http框架。
import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
async with session.get("http://httpbin.org/headers") as response:
print(await response.text())
asyncio.run(main())
(五)异步迭代器
(1)什么是异步迭代器?
- 实现了 aiter() 和 anext()方法的对象。
- anext 必须返回一个 awaitable 对象。
- async for会处理异步迭代器的 anext()方法所返回的可等待对象,直到其引发一个 StopAsyncIteration异常。
(2)什么是异步可迭代对象?
- 可在 async for语句中被使用的对象。
- 必须通过它的 aiter()方法返回一个 asynchronous iterator 。
import asyncio
class Reader:
""" 自定义异步迭代器(同时也是异步可迭代对象) """
def __init__(self):
self.count = 0
async def readline(self):
self.count += 1
if self.count == 100:
return None
return self.count
def __aiter__(self):
return self
async def __anext__(self):
val = await self.readline()
if val is None:
raise StopAsyncIteration
return val
async def fn():
# 创建异步可迭代对象
async_iter = Reader()
# async for 必须放在async def 函数内,否则语法错误。
async for item in async_iter:
print(item)
asyncio.run((fn()))
(六)异步上下文管理器
- 此种对象通过定义
__aenter__()
和__aexit__()
方法来对 async with 语句中的环境进行控制。
import asyncio
class AsyncContextManager:
def __init__(self):
self.conn = None
async def do_something(self):
# 异步操作数据库
return 123
async def __aenter__(self):
# 异步链接数据库
self.conn = await asyncio.sleep(1)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
# 异步关闭数据库链接
await asyncio.sleep(1)
async def fn():
async with AsyncContextManager() as f:
res = await f.do_something()
print(res)
asyncio.run(fn())
(七)小结
- 只要看到async 和 await关键字
- 其内部就是基于协程实现的异步编程
- 这周异步编程是通过一个线程在IO等待事件去执行其他的协程任务,从而实现并发