web-dev-qa-db-ja.com

Pythonで非同期ジェネレータを作成する方法は?

このPython2.7コードを新しい非同期の世界の順序に書き換えようとしています。

_def get_api_results(func, iterable):
    pool = multiprocessing.Pool(5)
    for res in pool.map(func, iterable):
        yield res
_

map()はすべての結果が計算されるまでブロックするので、これを非同期の実装として書き直して、準備ができ次第結果を生成するようにしています。 map()と同様に、戻り値はiterableと同じ順序で返す必要があります。私はこれを試しました(レガシー認証要件のためにrequestsが必要です):

_import requests

def get(i):
    r = requests.get('https://example.com/api/items/%s' % i)
    return i, r.json()

async def get_api_results():
    loop = asyncio.get_event_loop()
    futures = []
    for n in range(1, 11):
        futures.append(loop.run_in_executor(None, get, n))
    async for f in futures:
        k, v = await f
        yield k, v

for r in get_api_results():
    print(r)
_

Python 3.6を使用すると、次のようになります。

_  File "scratch.py", line 16, in <module>
    for r in get_api_results():
TypeError: 'async_generator' object is not iterable
_

どうすればこれを達成できますか?

13

古い(2.7)コードについて-マルチプロセッシングは、CPU集中型のタスクを同時に処理するための非常に単純なスレッディングモジュールの強力なドロップイン置換と見なされ、スレッディングはあまり機能しません。あなたのコードはおそらくHTTPリクエストを行う必要があるだけなので、CPUバウンドではありません-そして問題を解決するにはスレッド化で十分だったかもしれません。

ただし、threadingを直接使用する代わりに、Python 3+には concurrent.futures と呼ばれるNiceモジュールがあり、クールなExecutor classes。このモジュールは、python 2.7 external package としても利用できます。

次のコードは、python 2およびpython 3で機能します。

_# For python 2, first run:
#
#    pip install futures
#
from __future__ import print_function

import requests
from concurrent import futures

URLS = [
    'http://httpbin.org/delay/1',
    'http://httpbin.org/delay/3',
    'http://httpbin.org/delay/6',
    'http://www.foxnews.com/',
    'http://www.cnn.com/',
    'http://europe.wsj.com/',
    'http://www.bbc.co.uk/',
    'http://some-made-up-domain.coooom/',
]


def fetch(url):
    r = requests.get(url)
    r.raise_for_status()
    return r.content


def fetch_all(urls):
    with futures.ThreadPoolExecutor(max_workers=5) as executor:
        future_to_url = {executor.submit(fetch, url): url for url in urls}
        print("All URLs submitted.")
        for future in futures.as_completed(future_to_url):
            url = future_to_url[future]
            if future.exception() is None:
                yield url, future.result()
            else:
                # print('%r generated an exception: %s' % (
                # url, future.exception()))
                yield url, None


for url, s in fetch_all(URLS):
    status = "{:,.0f} bytes".format(len(s)) if s is not None else "Failed"
    print('{}: {}'.format(url, status))
_

このコードは、スレッド化に基づいて_futures.ThreadPoolExecutor_を使用します。マジックの多くは、ここで使用されるas_completed()にあります。

あなたのpython 3.6上記のコード、 run_in_executor() を使用してfutures.ProcessPoolExecutor()を作成し、非同期IOを実際には使用しません!!

本当にasyncioを使用したい場合は、 aiohttp など、asyncioをサポートするHTTPクライアントを使用する必要があります。次にコード例を示します。

_import asyncio

import aiohttp


async def fetch(session, url):
    print("Getting {}...".format(url))
    async with session.get(url) as resp:
        text = await resp.text()
    return "{}: Got {} bytes".format(url, len(text))


async def fetch_all():
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, "http://httpbin.org/delay/{}".format(delay))
                 for delay in (1, 1, 2, 3, 3)]
        for task in asyncio.as_completed(tasks):
            print(await task)
    return "Done."


loop = asyncio.get_event_loop()
resp = loop.run_until_complete(fetch_all())
print(resp)
loop.close()
_

ご覧のとおり、asyncioにはas_completed()もあり、実際の非同期IOを使用して、1つのプロセスで1つのスレッドのみを使用しています。

12
Udi

イベントループを別のコルーチンに配置します。それをしないでください。イベントループは非同期コードの最も外側の「ドライバー」であり、同期して実行する必要があります。

取得した結果を処理する必要がある場合は、処理するコルーチンをさらに記述します。キューからデータを取得することも、直接フェッチを実行することもできます。

たとえば、結果をフェッチして処理するメイン関数を持つことができます。

_async def main(loop): 
    for n in range(1, 11):
        future = loop.run_in_executor(None, get, n)
        k, v = await future
        # do something with the result

loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
_

aiohttp のような非同期ライブラリを使用して、get()関数も適切に非同期にするため、executorを使用する必要はありません。

6
Martijn Pieters