Python 3.6の概要 (その3 - async関連)
非同期ジェネレータ
現在のPythonでは、ジェネレータを使って、とてもお手軽にイテレータを作成できる。例えば、奇数列を生成するジェネレータは、次のように書ける。
def odds(): i = 1 while True: yield i i += 2
しかし、ジェネレータが存在しなかった頃のPythonでは、わざわざ__iter__
メソッドなどの特殊メソッドを実装したクラスを定義し、
class Odds: def __init__(self): self._cur = 1 def __iter__(self): return self def next(self): ret = self._cur self._cur += 2 return ret
などと書かなければならなかった。
Python3.5で導入された コルーチン は、イテレータと同様な概念として 非同期イテレータ をサポートしているが、ジェネレータに相当する機能が存在しないため、コルーチンを使用したイテレータを作成するのは非常に面倒な作業となっていた。
このため、Python3.6ではあらたに非同期イテレータを作成する、非同期ジェネレータが提供され、コルーチン内でも普通の関数と同様に yield
式を使って非同期ジェネレータを作成できるようになった PEP 525 -- Asynchronous Generators。
import asyncio # spam()は非同期ジェネレータ async def spam(): await asyncio.sleep(1) yield 'spam1' await asyncio.sleep(2) yield 'spam2' async def spam_restrant(): async for s in spam(): #非同期forループ print(s) loop = asyncio.get_event_loop() loop.run_until_complete(spam_restrant())
通常のジェネレータオブジェクトは、send()
, throw()
, close()
メソッドで通信できるが、非同期ジェネレータでも同様にasend()
, athrow()
, aclose()
メソッドを使用できる。
非同期内包
リスト内包などの内包式で、非同期for
ループを指定できるようになった。PEP 530 -- Asynchronous Comprehensions
import asyncio async def spam(n): for i in range(n): yield f'spam{i}' async def spam_restrant(): print([s async for s in spam(3)]) loop = asyncio.get_event_loop() loop.run_until_complete(spam_restrant())
また、内包式中に、 await
式を指定できるようになった。
import asyncio async def spam(n): await asyncio.sleep(0.1) return f'spam:{n}' async def pred(n): return n % 2 async def spam_restrant(): print({n:await spam(n) for n in range(5) if await pred(n)}) loop = asyncio.get_event_loop() loop.run_until_complete(spam_restrant())