web-dev-qa-db-ja.com

Python 3.4の "と非同期"

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に、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)

ただし、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で動作するように変換するにはどうすればよいですか?

21
Imran

session.get()の結果をコンテキストマネージャーとして使用しないでください。代わりにコルーチンとして直接使用してください。 session.get()が生成するリクエストコンテキストマネージャーは、通常、 release終了時にリクエスト 、ただし、 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() awaitableへのアクセスの間にさらにステートメントがある場合、おそらくとにかくtry:..finally:を使用して接続を解放する必要があります。 Python 3.5リリースコンテキストマネージャもcloses例外が発生した場合の応答。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()
17
Martijn Pieters

aiohttpの-​​ 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"
_

例外に加えて、「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()を明示的に呼び出す方が良いと思います。

5