异步上下文管理器

Asynchronous context manager

我有一个 asynchronous API,我用它来连接 SMTP 服务器并向其发送邮件,该服务器具有一些设置和拆卸功能。所以它非常适合使用 Python 3 的 contextlib.

中的 contextmanager

虽然,我不知道是否可以写,因为他们都使用生成器语法来写。

这可能会说明问题(包含 yield-base 和 async-await 语法的混合,以说明异步调用和 yield 到上下文管理器之间的区别)。

@contextmanager
async def smtp_connection():
    client = SMTPAsync()
    ...

    try:
        await client.connect(smtp_url, smtp_port)
        await client.starttls()
        await client.login(smtp_username, smtp_password)
        yield client
    finally:
        await client.quit()

目前在python范围内可以做这种事情吗?如果是,我将如何使用 with as 语句?如果没有,是否有其他方法可以实现此目的 - 也许使用旧式上下文管理器?

感谢@jonrsharpe 能够制作异步上下文管理器。

对于任何想要示例代码的人来说,这是我最终的样子:

class SMTPConnection():
    def __init__(self, url, port, username, password):
        self.client   = SMTPAsync()
        self.url      = url
        self.port     = port
        self.username = username
        self.password = password

    async def __aenter__(self):
        await self.client.connect(self.url, self.port)
        await self.client.starttls()
        await self.client.login(self.username, self.password)

        return self.client

    async def __aexit__(self, exc_type, exc, tb):
        await self.client.quit()

用法:

async with SMTPConnection(url, port, username, password) as client:
    await client.sendmail(...)

如果我做错了什么,请随时指出。

asyncio_extras 包有一个很好的解决方案:

import asyncio_extras

@asyncio_extras.async_contextmanager
async def smtp_connection():
    client = SMTPAsync()
    ...

对于 Python < 3.6,您还需要 async_generator 包并将 yield client 替换为 await yield_(client)

从Python 3.7开始,你可以这样写:

from contextlib import asynccontextmanager

@asynccontextmanager
async def smtp_connection():
    client = SMTPAsync()
    ...

    try:
        await client.connect(smtp_url, smtp_port)
        await client.starttls()
        await client.login(smtp_username, smtp_password)
        yield client
    finally:
        await client.quit()

在 3.7 之前,您可以使用 async_generator 包。在 3.6 上,你可以这样写:

# This import changed, everything else is the same
from async_generator import asynccontextmanager

@asynccontextmanager
async def smtp_connection():
    client = SMTPAsync()
    ...

    try:
        await client.connect(smtp_url, smtp_port)
        await client.starttls()
        await client.login(smtp_username, smtp_password)
        yield client
    finally:
        await client.quit()

如果你想一直回到 3.5,你可以这样写:

# This import changed again:
from async_generator import asynccontextmanager, async_generator, yield_

@asynccontextmanager
@async_generator      # <-- added this
async def smtp_connection():
    client = SMTPAsync()
    ...

    try:
        await client.connect(smtp_url, smtp_port)
        await client.starttls()
        await client.login(smtp_username, smtp_password)
        await yield_(client)    # <-- this line changed
    finally:
        await client.quit()