Python 3.4中的"async with"

33
aiohttp的入门文档提供以下客户端示例: ```

The Getting Started docs for aiohttp give the following client example:

```
import asyncio
import aiohttp

async def fetch_page(session, url):
    with aiohttp.Timeout(10):
        async with session.get(url) as response:
            assert response.status == 200
            return await response.read()

loop = asyncio.get_event_loop()
with aiohttp.ClientSession(loop=loop) as session:
    content = loop.run_until_complete(
        fetch_page(session, 'http://python.org'))
    print(content)

他们针对Python 3.4用户给出以下说明:

如果您正在使用Python 3.4,请将await替换为yield from,将async def替换为@coroutine装饰器。

如果我按照这些说明操作,我会得到:

import aiohttp
import asyncio

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(10):
        async with session.get(url) as response:
            return (yield from response.text())

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    with aiohttp.ClientSession(loop=loop) as session:
        html = loop.run_until_complete(
            fetch(session, 'http://python.org'))
        print(html)

不过,这段代码无法运行,因为Python 3.4不支持async with语法:

$ python3 client.py 
  File "client.py", line 7
    async with session.get(url) as response:
             ^
SyntaxError: invalid syntax

我该如何翻译async with语句,以便与Python 3.4一起使用?

2个回答

21

不要将 session.get() 的结果用作上下文管理器;而是直接将其用作协程。session.get() 生成的请求上下文管理器通常会在退出时释放请求,但使用 response.text() 也会做到这一点,因此在这里可以忽略它:

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(10):
        response = yield from session.get(url)
        return (yield from response.text())

这里返回的请求包装器缺少必需的异步方法(__aenter____aexit__),如果不使用Python 3.5,则会完全省略它们(请参见相关源代码)。

如果在session.get()调用和访问response.text()可等待对象之间有更多语句,您可能仍想使用try:..finally:来释放连接。Python 3.5发布的上下文管理器还会在出现异常时关闭响应。因为这里需要yield from response.release(),所以在Python 3.4之前无法将其封装在上下文管理器中:

import sys

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(10):
        response = yield from session.get(url)
        try:
            # other statements
            return (yield from response.text())
        finally:
            if sys.exc_info()[0] is not None:
                # on exceptions, close the connection altogether
                response.close()
            else:
                yield from response.release()

6

aiohttp示例采用3.4语法实现。基于json客户端示例,您的函数应该是:

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(10):
        resp = yield from session.get(url)
        try:
            return (yield from resp.text())
        finally:
            yield from resp.release()

更新:

请注意,Martijn的解决方案适用于简单情况,但在特定情况下可能会导致不良行为:

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(5):
        response = yield from session.get(url)

        # Any actions that may lead to error:
        1/0

        return (yield from response.text())

# exception + warning "Unclosed response"

除了异常之外,您还会收到“未关闭的响应”警告。这可能会导致复杂应用程序中的连接泄漏。如果手动调用 resp.release()/resp.close(),则可以避免此问题:

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(5):
        resp = yield from session.get(url)
        try:

            # Any actions that may lead to error:
            1/0

            return (yield from resp.text())
        except Exception as e:
            # .close() on exception.
            resp.close()
            raise e
        finally:
            # .release() otherwise to return connection into free connection pool.
            # It's ok to release closed response:
            # https://github.com/KeepSafe/aiohttp/blob/master/aiohttp/client_reqrep.py#L664
            yield from resp.release()

# exception only

我认为最好遵循官方示例(以及__aexit__实现)并显式调用resp.release()/resp.close()


谢谢您指引我那些例子。我之前没有发现它们。 - Imran
2
请注意,在发生异常的情况下,通常希望关闭响应。 - Martijn Pieters
1
@MartijnPieters 谢谢,你说得对。我修复了代码以匹配“_RequestContextManager.__aexit__”逻辑。 - Mikhail Gerasimov

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