"与"异步在Python 3.4中

时间:2016-05-26 15:59:35

标签: python python-3.x async-await python-asyncio aiohttp

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

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,请将from替换为来自和的yield   使用@coroutine装饰器进行异步def。

如果我遵循这些说明,我会得到:

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 个答案:

答案 0 :(得分:17)

不要将session.get()的结果用作上下文管理员;直接用它作为协程。 session.get()生成的请求上下文管理器在退出时通常会release the request,但so does using 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时它们完全省略(参见relevant source code)。

如果您在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()

答案 1 :(得分:5)

aiohttp使用3.4语法实现的examples。基于json client example,您的功能将是:

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

<强> UPD:

请注意,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__ implementation)并明确致电resp.release() / resp.close()