Python异步上下文管理器

4
在Python语言参考3.4.4中,指出__aenter__()__aexit__()必须返回可等待对象。然而,在异步上下文管理器示例中,这两个方法返回None:
class AsyncContextManager:
    async def __aenter__(self):
        await log('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        await log('exiting context')

这段代码是否正确?

2个回答

7

__aenter____aexit__必须返回可等待对象,但是当您调用示例中的这些函数时会发生什么呢:

>>> class AsyncContextManager:
...     async def __aenter__(self):
...         await log('entering context')
...     async def __aexit__(self, exc_type, exc, tb):
...         await log('exiting context')
... 
>>> AsyncContextManager().__aenter__()
<coroutine object AsyncContextManager.__aenter__ at 0x7f5b092d5ac0>

它没有返回None!我们得到了一个协程对象,这是可等待的。

这些方法是async函数,自动返回(可等待)的异步协程。在async函数体中的return语句决定了当您await协程时返回什么,而不是调用函数时返回什么。

这类似于生成器函数返回生成器迭代器,即使它们通常没有return语句,以及如果您将__iter__编写为生成器函数,则不应尝试在生成器函数内部return迭代器。


如果您在定义为async函数的__aenter____aexit__中放置return语句会发生什么?您可以这样做,如果这样做,return语句不必返回可等待对象。以下是Python的处理方式。
如果您从定义为async函数的__aenter__return了某些内容,则决定将其绑定到as目标的内容(如果async with使用as)。
如果您从定义为async函数的__aexit__return了某些内容,则确定是否抑制在块内引发的异常。"真值"表示告诉async with抑制异常,而"假值"表示告诉async with让异常传播。默认值None为假值,因此默认情况下不会抑制异常。
以下是一个示例:
import asyncio

class Example:
    async def __aenter__(self):
        return 3
    async def __aexit__(self, exc_type, exc, tb):
        return True

async def example():
    async with Example() as three:
        print(three == 3)
        raise Exception
    print("Exception got suppressed")

asyncio.run(example())

输出:

True
Exception got suppressed

我觉得这里的第一句话非常误导人,它关注低级实现细节而不是实际的大局抽象。是的,生成器函数返回生成器迭代器,async 函数返回本地协程对象。但是,考虑到 async def f(): return 123,几乎每个人都会说 "f 返回 123",而考虑到 async def f(): return None,几乎每个人都会说 "f 返回 None" - 因为这通常是“返回”的最有用的含义和抽象级别。在 Python 中,没有 returnreturn None 是相同的。 - mtraceur
2
@mtraceur:我不同意;低级实现细节对于解决误解至关重要。语言参考指出__aenter____aexit__必须返回可等待对象,而解决问题的关键是认识到这些__aenter____aexit__方法确实返回可等待对象,尽管缺少return语句。话虽如此,我会扩展答案以澄清。 - user2357112
我不认为“异步函数返回协程对象”是一种实现细节。它是异步公共API的核心部分,也是在使用asyncio时必须理解的关键概念。 - user2357112
抱歉,经过进一步的思考,我同意您的主要观点 - 它确实直接相关于解决提问者的误解。我犯了一个错误,让自己对这个答案做出了一个仓促的判断,而没有真正消化问题及其所需的内容。 - mtraceur

3

你的__aenter__方法必须返回一个上下文。

class MyAsyncContextManager:
    async def __aenter__(self):
        await log('entering context')
        # maybe some setup (e.g. await self.setup())
        return self

    async def __aexit__(self, exc_type, exc, tb):
        # maybe closing context (e.g. await self.close())
        await log('exiting context')

    async def do_something(self):
        await log('doing something')

使用方法:

async with MyAsyncContextManager() as context:
    await context.do_something()

2
并不完全正确。__aenter__允许返回某些内容的,但它并不一定要这样做。并非所有上下文管理器(异步或非异步)都需要该功能。(此外,如果它确实返回了某些内容,那么这个东西不一定是“上下文”)。 - user2357112

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接