首页 文章

Python 3.4中的“异步”

提问于
浏览
20

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,请使用@coroutine装饰器替换等于yield和async 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 回答

  • 17

    aiohttpexamples使用3.4语法实现 . 基于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"
    

    除了例外你'll get also warning 2440837 . This may lead to connections leak in complex app. You will avoid this problem if you' ll手动调用 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() .

  • 5

    只是不要使用 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()
    

相关问题