第六章
一文搞定asyncio中哪些对象是可以await的
前面文章从宏观层面分析了Python asyncio编程的核心原理,从本篇开始将进入编写异步代码的视角。Python中的async
、await
创建的原生协程出自PEP492,地址:https://peps.python.org/pep-0492/。
async协程
async/await
是Python中实现异步编程的关键字。在Python 3.5及以上版本,可用async def
声明异步函数,并在函数内用await
等待异步操作完成。
具体来说,async def
声明的函数返回一个协程对象,可被事件循环调度。协程内用await
时,会暂停执行,等待异步操作完成再继续。
分析协程对象示例:
import inspect
async def f(): # Step1: 定义协程
return 123
type(f) # Step2: 查看协程对象定义
# <class 'function'>
inspect.iscoroutinefunction(f) # Step3
# True
async
关键字定义协程。- 通过
type
查看对象类型。 - 导入
inspect
包分析对象。 inspect.iscoroutinefunction
判断函数是否为协程对象。
迭代器协程
对比早期函数迭代器对象:
def g():
yield 123
type(g)
# <class 'function'>
gen = g()
type(gen)
# <class 'generator'>
在Python中,生成器函数含yield
关键字时成为协程(Coroutine)。协程函数可暂停保存状态,用yield
暂停,send
方法发送值恢复执行。
async协程 VS 迭代器协程
PEP-492强调原生协程和基于生成器的协程不同。原生协程内执行yield
是错误,但两者有互操作性。
示例代码(出自Python官方包):
@types.coroutine
def _sleep0():
"""Skip one event loop run cycle.
This is a private helper for asyncio.sleep(), used when the 'delay' is set to 0. It uses a bare 'yield' expression (which Task._step knows how to handle) instead of creating a Future object.
"""
yield
async def sleep(delay, result=None, *, loop=None):
"""Coroutine that completes after a given time (in seconds).
If delay is 0, it uses _sleep0() instead of creating a Future.
"""
if delay <= 0:
await _sleep0()
return result
if loop is None:
loop = events.get_running_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.", DeprecationWarning, stacklevel=2)
future = loop.create_future()
h = loop.call_later(delay, futures._set_result_unless_cancelled, future, result)
try:
return await future
finally:
h.cancel()
当用types.Coroutine
装饰器装饰基于生成器的协程,它成为可被await
的协程。原生协程等待基于生成器的协程B,通过send
运行:
- 若B产生值,直接作为
send
调用结果返回给A的send
调用者。 - B暂停,再次调用A的
send
,恢复B,B的yield
评估为send
调用的参数。 - 若B返回或引发
StopIteration
,返回值或StopIteration
的值在A中作为await
语句的值可见。
什么对象是可await的
基于PEP492,Python的await
等待的协程支持场景:
- 另一个原生协程:用
async def
定义,通过await
等待执行结果。 - 包装了基于生成器的协程的对象:用
asyncio
模块的asyncio.coroutine
函数包装,然后用await
等待执行结果。 - 实现了
__await__
方法的对象:通常用asyncio
模块的asyncio.Future
类实现未来对象,用await
等待执行结果。
除这些类型,不能用await
等待其他对象。使用await
语句须在异步上下文中(原生协程、被包装的生成器协程、事件循环)。
原生协程作为await目标
类似yield from
,协程恢复执行,运行到自己的await
语句。例如asyncio.sleep
,会休眠指定秒数,await
语句暂停协程,直到时间过去。
基于生成器的协程为await目标
@types.coroutine
装饰的生成器函数,如前面的_sleep0
。
实现了__await__
方法的协程对象作为await目标(Future)
class AsyncAwaitable:
async def __await__(self):
print("Awaiting for 2 seconds...")
await asyncio.sleep(2)
return "Result"
async def main():
print("Main coroutine started")
result = await AsyncAwaitable()
print(f"Result: {result}")
print("Main coroutine ended")
asyncio.run(main())
注意__await__
方法须返回迭代器对象,通常用asyncio.Future
类实现未来对象。示例中用asyncio.sleep
模拟耗时操作,返回字符串作为结果。
ioloop的穿针引线功能
ioloop
(事件循环)是异步编程核心,任何Python循环中,asyncio
库的事件循环提供运行环境,使协程在其中调度执行,实现异步操作。
对于PEP492对象:
- 原生协程:用
async
关键字定义,函数等待另一个原生协程的执行结果。调用协程像普通函数,但返回协程对象,不会启动执行,用await
语句等待协程对象时,事件循环开始执行。 - 任务(Task):高级API,包装协程,用
asyncio.create_task
创建,放入事件循环调度。任务自身也是awaitable
对象,可使调用await
等待任务的结果。 - 未来对象(Future):占位符,代表异步操作的未完成结果,类似Promise(如JavaScript)。完成时通过调用
set_result
方法设置结果,可用await
语句等待。
事件循环在协程、任务或未来对象间切换,每个可await
对象都有用途,根据具体情况选择使用。
第七章
Python中协程的生命周期
协程的几种状态详解
Python的asyncio协程的生命周期可以分为以下几种状态:
- CORO_CREATED:协程对象已经被创建,但还没有被调度执行。
- CORO_RUNNING:协程对象正在执行中。
- CORO_SUSPENDED:协程对象被挂起,等待事件循环调度执行。
- CORO_CLOSED:协程对象已经执行完毕,或者被取消。
通过状态图表示协程如下:
- start:协程刚创建时的状态。
- running:协程正在执行的状态。
- pending:协程等待执行的状态,即已经被添加到事件循环中,但还没有开始执行。
- done:协程执行完毕的状态,可能会包含返回值或异常信息。
- cancelled:协程被取消的状态。
协程从start状态开始,经过事件循环的调度,进入running状态。如果协程执行完毕,会转换到done状态,并可能包含返回值或异常信息。如果协程还没有开始执行,或者在执行过程中被挂起,会转换到pending状态。在pending状态下,协程可以被重新调度进入running状态,也可以直接转换到done状态,表示协程执行完成。最后,如果协程被取消,会进入cancelled状态。
创建协程:CORO_CREATED
在Python的asyncio库中,创建协程的方式有以下几种:
- 使用
async def
关键字定义一个异步函数:函数体中包含await
语句。这种方式创建的协程被称为原生协程(native coroutine),是最常见的创建协程的方式。例如:
async def my_coroutine():
# 异步操作
await asyncio.sleep(1)
- 使用
asyncio.coroutine
装饰器定义一个生成器函数:函数体中包含yield from
语句。这种方式创建的协程被称为旧式协程(legacy coroutine),已经不推荐使用。例如:
@asyncio.coroutine
def my_coroutine():
# 异步操作
yield from asyncio.sleep(1)
- 使用
asyncio.ensure_future
函数:将一个可await
对象转换为一个任务对象(Task),并将任务对象添加到事件循环中执行。这种方式可以将任何可await
对象(包括原生协程、旧式协程和未来对象)转换为任务对象,方便在事件循环中调度执行。例如:
async def my_coroutine():
# 异步操作
await asyncio.sleep(1)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(my_coroutine())
loop.run_until_complete(task)
- 使用
asyncio.create_task
函数:将一个原生协程转换为任务对象,并将任务对象添加到事件循环中执行。这种方式创建任务对象的过程与上一种方式类似,但是只能用于原生协程。例如:
async def my_coroutine():
# 异步操作
await asyncio.sleep(1)
loop = asyncio.get_event_loop()
task = asyncio.create_task(my_coroutine())
loop.run_until_complete(task)
create_task
创建协程与ensure_future
创建的区别
在Python asyncio中,asyncio.create_task
和asyncio.ensure_future
都可以用来将一个协程转换成一个Task对象,并将该任务添加到事件循环中执行。它们的使用上的差异与优缺点如下:
- 差异:
- 参数类型不同:
create_task
的参数必须是一个协程对象,而ensure_future
的参数可以是一个协程对象、一个Future对象或一个可await
对象。 - 返回值不同:
create_task
返回一个Task对象,而ensure_future
返回一个Future对象。由于Task类是Future类的子类,因此使用ensure_future
返回的对象也可以被await
语句添加到事件循环中执行。
- 参数类型不同:
- 优缺点:
create_task
的优点:可以保证返回的对象一定是一个Task对象,不需要再手动检查返回值的类型。此外,create_task
函数在Python 3.7及以上版本中才被引入,因此在新版本中建议使用create_task
函数。ensure_future
的优点:可以接受更多类型的参数,包括协程对象、Future对象和Task对象。此外,ensure_future
函数在Python 3.4中就已经存在,因此可以在旧版本中使用。- 两者的缺点:使用起来比较繁琐,需要手动将协程对象转换成Task对象并添加到事件循环中执行。为了简化操作,Python 3.7及以上版本中引入了
asyncio.create_task
函数,可以直接将协程对象转换成Task对象并添加到事件循环中执行。
如果你的Python版本在3.7及以上,建议使用create_task
函数;如果需要接受更多类型的参数,或者需要在旧版本中使用,可以使用ensure_future
函数。
CORO_RUNNING & CORO_SUSPENDED
- CORO_RUNNING:协程对象正在执行中。例如:
async def my_coroutine():
result = 0
for i in range(100000):
result += i
await asyncio.sleep(1) # 等待一个可await对象的执行结果
在以上代码中,协程先执行一些CPU密集型的计算任务,这会使得协程一直处于CORO_RUNNING状态,直到计算任务执行完毕。接着,在协程中使用await
语句等待一个可await
对象的执行结果时,协程会被挂起,进入CORO_SUSPENDED状态。
- CORO_SUSPENDED:协程对象被挂起,等待事件循环调度执行。例如:
async def my_coroutine():
await asyncio.sleep(1) # 等待一个可await对象的执行结果
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(my_coroutine())
loop.run_until_complete(task)
在以上代码中,协程使用await
语句等待一个可await
对象的执行结果时,协程会被挂起,进入CORO_SUSPENDED状态。接着,将协程转换为任务对象并添加到事件循环中执行时,协程会被调度执行,进入CORO_RUNNING状态。在等待可await
对象的执行结果时,协程可能会被多次挂起和运行,进入CORO_SUSPENDED和CORO_RUNNING两种状态交替出现。
协程关闭状态:CORO_CLOSED
在Python的asyncio库中,协程进入CORO_CLOSED状态的方式有以下几种:
- 协程执行完毕:当协程中的代码执行完毕后,协程会自动进入CORO_CLOSED状态。例如:
async def my_coroutine():
# 执行一些异步操作
await asyncio.sleep(1)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(my_coroutine())
loop.run_until_complete(task)
# 协程执行完毕,进入CORO_CLOSED状态
在以上代码中,协程中的异步操作执行完毕后,协程会自动进入CORO_CLOSED状态。
- task被取消:在事件循环中,可以使用
task.cancel()
方法取消一个任务,如果该CORO对象对应的协程还没有开始执行或在执行过程中被取消,协程会被取消并进入CORO_CLOSED状态。例如:
async def my_coroutine():
# 执行一些异步操作
await asyncio.sleep(1)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(my_coroutine())
task.cancel()
loop.run_until_complete(task)
# 协程被取消,进入CORO_CLOSED状态
在以上代码中,使用task.cancel()
方法取消任务后,协程会进入CORO_CLOSED状态。
- 协程出现异常:如果协程在执行过程中发生异常,协程会进入CORO_CLOSED状态。例如:
async def my_coroutine():
# 执行一些异步操作
await asyncio.sleep(1)
raise ValueError("Oops!")
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(my_coroutine())
try:
loop.run_until_complete(task)
except ValueError:
pass
# 协程抛出异常,进入CORO_CLOSED状态
在以上代码中,协程中抛出了一个ValueError
异常,协程会进入CORO_CLOSED状态。
当协程进入CORO_CLOSED状态后,协程对象会被垃圾回收机制回收,协程的局部变量也会被销毁。如果需要在协程执行完毕后获取协程的执行结果,可以使用await
语句等待协程的返回值,或者使用asyncio.wait_for
函数等待协程的执行结果,并设置一个超时时间。
第八章
一文搞定异步迭代器在异步IO中的用法
迭代是Python中的基本操作,我们可以迭代列表、字符串及各种其他结构。Asyncio允许我们开发异步迭代器,通过定义实现__aiter__()
和__anext__()
方法的对象,在asyncio程序中创建和使用异步迭代器。
什么是异步迭代器
在Python的异步编程模块asyncio中,异步迭代器(Asynchronous Iterators)是一种特殊的迭代器,允许异步生成值。它实现了__aiter__()
和__anext__()
方法,其中__aiter__()
返回异步迭代器对象本身,__anext__()
返回一个coroutine协程对象,用于异步生成下一个值。
异步迭代器工作方式与常规迭代器类似,但允许在迭代过程中异步生成值。当用于async for
循环时,会异步生成迭代序列中的每个值,直到没有值可供生成。
示例代码:
import asyncio
class AsyncRange:
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __aiter__(self): # 注意__aiter__必须为普通方法
return self
async def __anext__(self): # 此处为真正的异步方法
if self.start >= self.stop:
raise StopAsyncIteration
value = self.start
self.start += 1
await asyncio.sleep(1) # 模拟异步操作
return value
async def main():
async for i in AsyncRange(0, 5):
print(i)
asyncio.run(main())
此示例定义了AsyncRange
类,实现__aiter__()
和__anext__()
方法,异步生成指定范围内的整数序列。__anext__()
中用await asyncio.sleep(1)
模拟异步操作,最后在main()
中用async for
循环迭代异步迭代器并打印值。
异步迭代器只能在异步上下文中使用,如在协程函数或回调函数中。使用时可用async for
循环或用asyncio.create_task()
函数将其包装成Task对象。
async for
介绍
在Python的异步编程模块asyncio中,async for
语法用于异步迭代器,允许在协程中异步生成值。其语法结构与常规for
循环类似,但使用异步迭代器迭代序列中的值。基本形式:
async for item in async_iterator:
# 处理每个异步生成的item
其中async_iterator
是异步迭代器对象,每次迭代时异步生成一个值并赋值给item
变量,可用item
处理每个值。
async for
也可用于列表推导场景:
# 列表推导场景的async for用法
result = [i async for i in async_iter]
如何使用异步迭代器
在Python的asyncio模块中,通过实现__aiter__()
和__anext__()
方法定义异步迭代器对象。
- 首先,实现
__aiter__()
方法,返回异步迭代器对象本身,通常返回self
。 - 其次,在异步迭代时,每次迭代调用
__anext__()
方法生成序列中的值。若序列结束,抛出StopAsyncIteration
异常。__anext__()
需返回协程对象。
示例:
class Reader:
async def readline(self):
# 模拟读取操作
pass
def __aiter__(self):
return self
async def __anext__(self):
val = await self.readline()
if val is None:
raise StopAsyncIteration
return val
因为异步迭代器是协程,每个迭代都在事件循环中被调度和执行,可在迭代主体内执行和等待可等待对象。
传统迭代中,迭代器有对应使用方式,异步迭代器也类似:
- 直接创建异步迭代器:
# 示例代码
it = Reader()
# 使用await anext(iterable)
result = await anext(it)
# 或者 await it.__anext__()
result = await it.__anext__()
异步迭代器中常见的错误
错误1:使用next()
函数时出现的异步迭代器错误
尝试用内置函数next()
而非anext()
使用异步迭代器会出错。示例:
import asyncio
class AsyncIterator:
def __init__(self):
self.count = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.count >= 10:
raise StopAsyncIteration
self.count += 1
await asyncio.sleep(1)
return self.count
async def main():
gen = AsyncIterator()
# 迭代器迭代一次(会导致错误)
awaitable = next(gen) # 错误使用next()
result = await awaitable
asyncio.run(main())
执行报错,因为next()
函数期望普通迭代器,而收到实现不同接口的异步迭代器。
错误2:使用for
循环时出现的异步迭代器错误
用for
循环而非async for
迭代异步迭代器会失败。示例:
import asyncio
class AsyncIterator:
def __init__(self):
self.count = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.count >= 10:
raise StopAsyncIteration
self.count += 1
await asyncio.sleep(1)
return self.count
async def main():
for item in AsyncIterator(): # 错误使用for循环
print(item)
asyncio.run(main())
for
循环期望普通迭代器,收到异步迭代器会报错。
错误3:使用不带可等待对象的异步迭代器时的错误
若__anext__()
方法定义为普通方法而非async def
,async for
会报错。示例:
import asyncio
class AsyncIterator:
def __init__(self):
self.count = 0
def __aiter__(self):
return self
def __anext__(self): # 错误:普通方法
if self.count >= 10:
raise StopAsyncIteration
self.count += 1
return self.count
async def main():
async for item in AsyncIterator():
print(item)
asyncio.run(main())
async for
期望__anext__()
返回可等待对象,普通方法返回值非可等待对象会报错。
错误4:定义__aiter__
的时候使用async
关键字
__aiter__()
方法不应使用async def
定义。示例:
import asyncio
class AsyncRange:
async def __aiter__(self): # 错误:__aiter__用async def
return self
async def __anext__(self):
if self.start >= self.stop:
raise StopAsyncIteration
await asyncio.sleep(1)
return self.start
async def main():
async for i in AsyncRange(0, 5):
print(i)
asyncio.run(main())
__aiter__()
应是普通方法,用async def
定义会导致async for
无法正确识别迭代器,引发错误。
第九章
Python asyncio中的异步生成器
python中的生成器介绍
在Python中,生成器(Generator)是一种特殊的迭代器,它可以在每次迭代中动态地生成值,而不是一次性生成所有值并将其存储在内存中。这使得生成器非常适合处理大量数据或无限数据流,因为它们可以逐步生成数据,而不是一次性生成所有数据。
生成器可以通过使用yield
语句来定义。当函数中包含yield
语句时,该函数就成为生成器函数,它将返回一个生成器对象。每次调用生成器对象的__next__()
方法时,生成器函数将执行到yield
语句,然后暂停并返回yield
语句后面的值。下次调用__next__()
方法时,函数将从暂停的位置继续执行,直到再次遇到yield
语句。
例如,下面是一个简单的生成器函数,它生成斐波那契数列:
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
这个函数定义了一个无限的斐波那契数列生成器。每次调用生成器对象的__next__()
方法时,它将返回下一个斐波那契数,并继续计算下一个数。由于它是一个无限的生成器,因此你可以一直调用__next__()
方法,直到停止程序或者手动停止生成器。
生成器非常适合处理大量数据或无限数据流,因为它们可以逐步生成数据,而不是一次性生成所有数据。生成器还可以与Python的迭代工具(如for
循环)一起使用,使代码更加简洁和高效。
# 创建一个生成器
gen = generator()
# 单步执行
result = next(gen)
# 迭代执行
results = [item for item in generator()]
异步generator介绍
异步生成器是Python 3.5引入的一项语言特性,它是生成器(Generator)的异步版本,可以用于异步编程中的协程(Coroutine)。
与普通生成器不同,异步生成器可以使用async/await
语法进行定义和调用,它可以在生成器函数中使用await
关键字暂停和恢复执行,以便在异步任务完成之前暂停执行,然后在异步任务完成之后恢复执行。
当调用一个异步生成器时,它会立即返回一个异步迭代器(AsyncIterator),该迭代器可以在异步上下文中使用async for
循环进行迭代。异步迭代器是一种异步协议,它包含一个__aiter__
方法和一个__anext__
方法,分别用于返回异步迭代器本身和获取下一个异步值。
下面是一个简单的示例,展示了如何定义和使用一个异步生成器:
import asyncio
async def async_generator():
for i in range(5):
await asyncio.sleep(1)
yield i
async def main():
async for i in async_generator():
print(i)
asyncio.run(main())
在上面的示例中,async_generator
是一个异步生成器,它每隔一秒钟生成一个数字并暂停执行,然后由主函数main
使用async for
循环进行迭代,并打印每个生成的数字。
在使用异步生成器时,需要将其包装在一个协程中,并使用await
进行调用。另外,异步生成器只能在异步上下文中使用,例如在异步函数中或在asyncio.run()
中使用。
异步生成器和原生生成器的区别
在实现上,Python中的异步生成器和原生生成器之间主要有以下区别:
- 关键字:异步生成器使用
async/await
关键字来定义生成器函数,而通用生成器使用yield
关键字来定义生成器函数。 - 返回值:异步生成器使用
async for
或者async for...in...
语句来迭代生成器函数中产生的值,而通用生成器则使用for
循环或者next()
函数来迭代生成器函数中产生的值。 - 生成器函数:异步生成器函数必须是使用
async def
关键字定义的函数。这些函数可以在函数体中使用await
关键字,来等待异步I/O操作、异步计算等。通用生成器函数使用yield
关键字来产生数据,函数中使用yield
关键字来产生数据。 - 返回类型:异步生成器函数返回一个异步生成器对象,它是一个异步迭代器(async iterator),可以通过
async for
或者async for...in...
语句来迭代生成器函数中产生的值。通用生成器函数返回一个生成器(iterator),可以通过for
循环或者next()
函数来迭代生成器中产生的值。 - 执行方式:在异步生成器函数完成执行后恢复执行,从而实现异步地生成数据流。通用生成器函数在执行时也会暂停,但是在等待时会阻塞整个线程,从而无法同时执行其他任务。
异步生成器和原生生成器在实现上有很大的差异。异步生成器可以使用异步操作,从而异步I/O操作的场景。通用生成器则适用于一般的数据生成场景,可以使用同步操作来生成数据。
协程对象不实现__iter__
和__next__
方法。因此,它们不能被迭代或传递给iter()
、list()
、tuple()
和其他内置函数。它们也不能在for...in
循环中使用。尝试在本地协程对象上使用iter()
或next()
将导致TypeError
错误。禁止对本地协程对象使用yield from
,这样做将导致TypeError
错误。因此,为了在asyncio代码中使用本地协程对象,必须使用@asyncio.coroutine
装饰器。
异步生成器通常用场景
- 处理异步I/O操作:异步生成器可以使用异步I/O操作来生成数据流,从而避免在等待I/O操作完成时阻塞整个线程。因为网络I/O操作通常是非常耗时的,使用异步生成器可以使代码更加高效。
- 处理大量数据:异步生成器可以处理大量的数据,而无需一次性将所有数据加载到内存中。这在处理大型文件或数据库等场景下很有用,可以避免内存不足的问题。
- 处理实时数据:异步生成器可以实时生成数据流,这在处理实时数据流的场景下很有用,例如处理实时传感器数据、网络日志等。
通用生成器则适用于大部分需要生成数据流的场景,尤其是处理简单的同步操作的场景,例如处理列表、字典等数据结构。
因此,如果你需要处理异步I/O操作、大量数据或实时数据,那么使用异步生成器是更好的选择。如果你只需要处理简单的同步操作,那么使用通用生成器就可以了。
异步生成器的用法
在本节中,我们将仔细研究如何在asyncio程序中定义、创建、执行和遍历异步生成器。
让我们从如何定义异步生成器开始。
定义异步生成器
我们可以通过定义至少有一个yield
表达式的协程来定义异步生成器。这意味着函数是使用async def
表达式定义的。
例如:
# 定义异步生成器
async def async_in_generator():
for i in range(10):
yield i
由于异步生成器是协程,每个函数都返回一个协程对象。每个asyncio
都循环主体安排和执行。因此我们可以在生成器的主体中执行和等待可等待对象。
例如:
async def async_generator():
for i in range(10):
# 暂停并等待一段时间
await asyncio.sleep(1)
yield i
如何使用异步生成器
要使用异步生成器,我们必须创建生成器。这看起来像调用它,但实际上是创建并返回一个迭代器对象。
例如:
# 创建异步生成器
it = async_generator()
这返回一种名为异步生成器迭代器的异步迭代器类型。
遍历异步生成器
可以使用anext()
函数遍历经典的生成器一样。结果是一个可等待对象,需要等待它一样。
例如:
# 获取一个步骤的可等待对象
awaitable = anext(gen)
# 执行生成器的一个步骤并获取结果
result = await awaitable
这可以一步完成。
例如:
# 步进异步生成器
result = await anext(gen)
异步生成器也可以使用async for
表达式来迭代遍历,该表达式会自动等待每次循环迭代。
例如:
# 遍历异步生成器
async for result in async_generator():
print(result)
我们可以在教程中了解有关async for
表达式的更多信息。
我们还可以使用async for
表达式和async to
表达式在异步任务完成之后恢复执行。
例如:
# 使用异步for表达式收集生成器的结果
results = [item async for item in async_generator()]
我们可以探索如何使用“async for”表达式遍历异步生成器。
在“async for”循环遍历生成器前的示例以使用async for
循环遍历生成器。
这可能是异步生成器最常见的使用模式。
# 带有async for循环的异步生成器示例
# 定义异步生成器
async def async_generator():
for i in range(5):
# 模拟工作
await asyncio.sleep(1)
yield i
# 主协程
async def main():
async for item in async_generator():
print(item)
# 执行asyncio程序
asyncio.run(main())
运行该示例首先创建main()
协程,并将其用main()
协程运行并启动for
循环。
anext()
函数逐步生成器,返回可等待对象。然后循环等待可等待对象并检索一个值。重复此过程,暂停main
协程,执行生成器的一个迭代,然后暂停并恢复main
协程,直到生成器耗尽。这突出了如何使用async for
表达式遍历异步生成器。
执行上述代码结果如下:
0
1
2
3
4
第十章
一文搞定异步上下文管理器的应用
什么是异步上下文管理器
在Python asyncio中,异步上下文管理器(Async Context Manager)是一种支持异步操作的上下文管理器。与普通的上下文管理器(Context Manager)类似,它也可以使用with
语句来管理资源的生命周期,但方法需异步执行。
异步上下文管理器需实现__aenter__
和__aexit__
两个特殊方法。__aenter__
返回异步上下文管理器对象,__aexit__
用于清理资源。使用时,需用async with
语句包装,例如:
import aiohttp
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
data = await response.text()
return data
此例中,用aiohttp
的异步HTTP客户端,async with
管理HTTP会话周期,内部用await
执行异步请求。
异步上下文管理器执行过程:
- 调用
__aenter__
方法,获取上下文管理器对象。 - 调用对象的
__aenter__
方法,执行异步操作获取资源。 - 若有异常,调用
__aexit__
清理资源,传递异常。 - 无异常则执行
async with
内代码块。 - 调用
__aexit__
清理资源。
它方便管理异步操作资源周期,提高异步编程效率和可读性,常见于异步文件操作、数据库连接、HTTP请求等。
异步上下文管理器和同步上下文管理器区别
- 使用方式:同步用
with
,异步用async with
。 - 实现方法:同步实现
__enter__
和__exit__
,异步实现__aenter__
和__aexit__
。 - 方法调用:同步方法同步执行,异步方法异步执行。
- 异常处理:同步用
try...finally
,异步用async with
和__aexit__
。 - 可迭代性:同步可用
for
循环迭代,异步不行。
异步上下文示例
import asyncio
import aiofiles
class AsyncFileReader:
def __init__(self, file_path):
self.file_path = file_path
async def __aenter__(self):
self.file = await aiofiles.open(self.file_path, 'r')
return self.file
async def __aexit__(self, exc_type, exc, tb):
await self.file.close()
async def read_file(file_path):
async with AsyncFileReader(file_path) as file:
contents = await file.read()
return contents
async def main():
contents = await read_file('example.txt')
print(contents)
asyncio.run(main())
此例定义AsyncFileReader
管理异步文件读取。__aenter__
打开文件,__aexit__
关闭文件。read_file
用async with
包装,main
中调用。
异步上下文管理器常见问题
- 忘记await:
async with
内代码块需用await
等待异步操作,否则操作未完成就执行后续代码,结果不可预知。 - 忘记async:定义异步上下文管理器对象的
__aenter__
和__aexit__
需用async
关键字。 - 处理异常:
async with
内代码块抛异常,需正确清理资源并传递异常。 - 多次调用__aenter__:
async with
中__aenter__
只调一次,结束后__aexit__
也只调一次,多次调__aenter__
结果不可预知。 - 混淆异步和同步:注意异步与同步代码区别,用同步方式调异步
__aenter__
和__aexit__
可能导致程序阻塞或死锁。
场景的异步asyncio包中异步上下文应用
- aiohttp包中使用async with示例:
import aiohttp
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
data = await response.text()
return data
用aiohttp
的异步HTTP客户端,async with
管理会话周期,await
执行请求。
- aioredis包中使用async with示例:
import aioredis
async def get_redis_value(key):
async with aioredis.create_redis('redis://localhost') as redis:
value = await redis.get(key)
return value
用aioredis
的异步Redis客户端,async with
管理连接周期,await
执行Redis操作。
- asyncpg包中使用async with示例:
import asyncpg
async def query_data():
async with asyncpg.connect(user='user', password='password', database='db', host='127.0.0.1') as conn:
async with conn.transaction():
result = await conn.fetch('SELECT * FROM table')
return result
用asyncpg
的异步PostgreSQL客户端,async with
管理连接周期,执行异步数据库操作。
这些示例展示了常见Python包中用异步上下文管理器管理资源周期,提高异步编程效率和可读性。