このBlogは移転しました。今後は aish.dev を御覧ください。

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