【问题标题】:"async with" in Python 3.4Python 3.4 中的“异步”
【发布时间】:2016-05-26 15:59:34
【问题描述】:

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,请将 await 替换为 yield from 和 带有 @coroutine 装饰器的 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)

但是,这不会运行,因为async with 在 Python 3.4 中不受支持:

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

如何翻译 async with 语句以使用 Python 3.4?

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      更新:

      请注意,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()

      【讨论】:

      • 感谢您向我指出这些示例。我没有找到那些。
      • 请注意,如果出现异常,您通常希望关闭响应。
      • @MartijnPieters 谢谢,你是对的。我修复了代码以匹配 _RequestContextManager.__aexit__ 逻辑。
      猜你喜欢
      • 2016-03-24
      • 1970-01-01
      • 1970-01-01
      • 2017-05-24
      • 1970-01-01
      • 1970-01-01
      • 2018-09-18
      • 2011-03-27
      相关资源
      最近更新 更多