web-dev-qa-db-ja.com

非同期イテレータの実装

Per PEP-492 非同期イテレータを実装しようとしています。

async for foo in bar:
    ...

これは、ドキュメントの例と同様の簡単な例であり、インスタンス化と非同期反復の非常に基本的なテストが含まれています。

import pytest

class TestImplementation:
    def __aiter__(self):
        return self
    async def __anext__(self):
        raise StopAsyncIteration


@pytest.mark.asyncio  # note use of pytest-asyncio marker
async def test_async_for():
    async for _ in TestImplementation():
        pass

ただし、テストスイートを実行すると、次のように表示されます。

=================================== FAILURES ===================================
________________________________ test_async_for ________________________________

    @pytest.mark.asyncio
    async def test_async_for():
>       async for _ in TestImplementation():
E       TypeError: 'async for' received an invalid object from __aiter__: TestImplementation

...: TypeError
===================== 1 failed, ... passed in 2.89 seconds ======================

TestImplementationが無効に見えるのはなぜですか?私が知る限り、それはプロトコルを満たしています:

  1. オブジェクトは__aiter__メソッドを実装する必要があります...非同期イテレータオブジェクトを返します。
  2. 非同期イテレータオブジェクトは、__anext__メソッドを実装する必要があります... awaitableを返します。
  3. 反復を停止するには、__anext__StopAsyncIteration例外を発生させる必要があります。

これは、最新リリースバージョンのPython(3.5.1)、py.test(2.9.2)、およびpytest-asyncio(0.4.1)では失敗します。

10
jonrsharpe

あなたが読んだ場合 ドキュメントの少し下に それはそれを述べています(私の強調):

PEP 492は、CPython3.5.0で__aiter__メソッドとして定義され、返されることが期待されていました非同期イテレータへの待機可能な解決

3.5.2では(PEP 492が暫定的に受け入れられたため)、__aiter__プロトコルが更新され、非同期イテレータが直接返されるようになりました。

したがって、3.5.2(2016/6/27でリリース)より前のバージョンの場合、ドキュメントは、動作する非同期イテレーターの記述方法と少しずれています。 3.5.0および3.5.1の修正バージョンは次のようになります。

class TestImplementation:
    async def __aiter__(self):
  # ^ note
        return self
    async def __anext__(self):
        raise StopAsyncIteration

これは、終了時に導入されました bug#2724 そして データモデルのドキュメント で少し明確になり、下位互換性のあるコードを書く方法も示唆しています。

28
jonrsharpe

非同期イテレータはPython 3.6 PEP-525 を参照)に実装されています

その場合、async forを使用するためにTestImplementationはまったく必要ありません。 yield(PEP-525からの例)を使用できます。

async def ticker(delay, to):
    """Yield numbers from 0 to `to` every `delay` seconds."""
    for i in range(to):
        yield i
        await asyncio.sleep(delay)

その後、期待どおりにasync forを使用できます。

async for i in ticker(1, 10):                                                                     
    print(f'Tick #{i}')
0
KevinG