【python】Asyncio包学习6-10

第六章

一文搞定asyncio中哪些对象是可以await的

前面文章从宏观层面分析了Python asyncio编程的核心原理,从本篇开始将进入编写异步代码的视角。Python中的asyncawait创建的原生协程出自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
  1. async关键字定义协程。
  2. 通过type查看对象类型。
  3. 导入inspect包分析对象。
  4. 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等待的协程支持场景:

  1. 另一个原生协程:用async def定义,通过await等待执行结果。
  2. 包装了基于生成器的协程的对象:用asyncio模块的asyncio.coroutine函数包装,然后用await等待执行结果。
  3. 实现了__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协程的生命周期可以分为以下几种状态:

  1. CORO_CREATED:协程对象已经被创建,但还没有被调度执行。
  2. CORO_RUNNING:协程对象正在执行中。
  3. CORO_SUSPENDED:协程对象被挂起,等待事件循环调度执行。
  4. 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_taskasyncio.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 defasync 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中的异步生成器和原生生成器之间主要有以下区别:

  1. 关键字:异步生成器使用async/await关键字来定义生成器函数,而通用生成器使用yield关键字来定义生成器函数。
  2. 返回值:异步生成器使用async for或者async for...in...语句来迭代生成器函数中产生的值,而通用生成器则使用for循环或者next()函数来迭代生成器函数中产生的值。
  3. 生成器函数:异步生成器函数必须是使用async def关键字定义的函数。这些函数可以在函数体中使用await关键字,来等待异步I/O操作、异步计算等。通用生成器函数使用yield关键字来产生数据,函数中使用yield关键字来产生数据。
  4. 返回类型:异步生成器函数返回一个异步生成器对象,它是一个异步迭代器(async iterator),可以通过async for或者async for...in...语句来迭代生成器函数中产生的值。通用生成器函数返回一个生成器(iterator),可以通过for循环或者next()函数来迭代生成器中产生的值。
  5. 执行方式:在异步生成器函数完成执行后恢复执行,从而实现异步地生成数据流。通用生成器函数在执行时也会暂停,但是在等待时会阻塞整个线程,从而无法同时执行其他任务。

异步生成器和原生生成器在实现上有很大的差异。异步生成器可以使用异步操作,从而异步I/O操作的场景。通用生成器则适用于一般的数据生成场景,可以使用同步操作来生成数据。

协程对象不实现__iter____next__方法。因此,它们不能被迭代或传递给iter()list()tuple()和其他内置函数。它们也不能在for...in循环中使用。尝试在本地协程对象上使用iter()next()将导致TypeError错误。禁止对本地协程对象使用yield from,这样做将导致TypeError错误。因此,为了在asyncio代码中使用本地协程对象,必须使用@asyncio.coroutine装饰器。

异步生成器通常用场景

  1. 处理异步I/O操作:异步生成器可以使用异步I/O操作来生成数据流,从而避免在等待I/O操作完成时阻塞整个线程。因为网络I/O操作通常是非常耗时的,使用异步生成器可以使代码更加高效。
  2. 处理大量数据:异步生成器可以处理大量的数据,而无需一次性将所有数据加载到内存中。这在处理大型文件或数据库等场景下很有用,可以避免内存不足的问题。
  3. 处理实时数据:异步生成器可以实时生成数据流,这在处理实时数据流的场景下很有用,例如处理实时传感器数据、网络日志等。

通用生成器则适用于大部分需要生成数据流的场景,尤其是处理简单的同步操作的场景,例如处理列表、字典等数据结构。

因此,如果你需要处理异步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执行异步请求。

异步上下文管理器执行过程:

  1. 调用__aenter__方法,获取上下文管理器对象。
  2. 调用对象的__aenter__方法,执行异步操作获取资源。
  3. 若有异常,调用__aexit__清理资源,传递异常。
  4. 无异常则执行async with内代码块。
  5. 调用__aexit__清理资源。

它方便管理异步操作资源周期,提高异步编程效率和可读性,常见于异步文件操作、数据库连接、HTTP请求等。

异步上下文管理器和同步上下文管理器区别

  1. 使用方式:同步用with,异步用async with
  2. 实现方法:同步实现__enter____exit__,异步实现__aenter____aexit__
  3. 方法调用:同步方法同步执行,异步方法异步执行。
  4. 异常处理:同步用try...finally,异步用async with__aexit__
  5. 可迭代性:同步可用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_fileasync with包装,main中调用。

异步上下文管理器常见问题

  • 忘记awaitasync 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包中用异步上下文管理器管理资源周期,提高异步编程效率和可读性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值