异步上下文管理器

时间:2016-05-25 09:36:05

标签: python asynchronous contextmanager

我有asynchronous API我用来连接并发送邮件到SMTP服务器,该服务器有一些设置并拆除它。因此,它非常适合使用Python 3 contextmanager中的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()

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

3 个答案:

答案 0 :(得分:33)

在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()

答案 1 :(得分:15)

感谢@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(...)

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

答案 2 :(得分:9)

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

import asyncio_extras

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

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