异步上下文管理器

fre*_*bie 31 python asynchronous contextmanager

我有一个异步API,我用它连接并发送邮件到SMTP服务器,它有一些设置和拆除它.所以它很适合使用contextmanagerPython 3的contextlib.

虽然,我不知道它是否可能写,因为它们都使用生成器语法来编写.

这可能会证明问题(包含yield-base和async-await语法的混合,以演示异步调用和上下文管理器的收益之间的差异).

@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()
Run Code Online (Sandbox Code Playgroud)

目前python中有可能出现这种情况吗?with as如果是,我将如何使用声明?如果没有,我可以用另一种方式实现这一点 - 也许使用旧式上下文管理器?

Nat*_*ith 41

在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()
Run Code Online (Sandbox Code Playgroud)

在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()
Run Code Online (Sandbox Code Playgroud)

如果你想一直回到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()
Run Code Online (Sandbox Code Playgroud)


fre*_*bie 21

感谢@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()
Run Code Online (Sandbox Code Playgroud)

用法:

async with SMTPConnection(url, port, username, password) as client:
    await client.sendmail(...)
Run Code Online (Sandbox Code Playgroud)

如果我做了任何愚蠢的事,请随时指出.

  • 记得在你的 `__aexit__` 中处理异常。否则你会隐藏异常并得到奇怪的隐形错误。 (4认同)
  • 问题是如果你同时使用它两次,你的第二个输入的客户端将覆盖第一个,第一个的退出也会退出第二个。 (2认同)

Bar*_*son 9

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

import asyncio_extras

@asyncio_extras.async_contextmanager
async def smtp_connection():
    client = SMTPAsync()
    ...
Run Code Online (Sandbox Code Playgroud)

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