异步上下文管理器
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()
我有一个 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()