Реализация асинхронного итератора

Согласно 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__... возвращающий ожидаемое значение.
  3. Чтобы остановить итерацию, __anext__ должен вызвать исключение StopAsyncIteration.

Это не работает с последними выпущенными версиями Python (3.5.1), py.test (2.9.2) и pytest-asyncio (0.4.1).


person jonrsharpe    schedule 25.06.2016    source источник
comment
У меня работает с pytest 2.9.2. Какую версию ты используешь?   -  person Mike Müller    schedule 25.06.2016
comment
@MikeMüller pytest-2.8.5 и asyncio-0.3.0 плагин, посмотрим, поможет ли обновление...   -  person jonrsharpe    schedule 25.06.2016
comment
@MikeMüller нет, тот же результат с плагинами pytest-2.9.2 и asyncio-0.4.1   -  person jonrsharpe    schedule 25.06.2016
comment
Я использую Python 3.5.1.   -  person Mike Müller    schedule 25.06.2016
comment
У меня точно так же не получается, используя последнюю версию 3.5.0. работает с 3.6.   -  person Padraic Cunningham    schedule 25.06.2016
comment
@jonrsharpe, просто редактировал комментарий ^^   -  person Padraic Cunningham    schedule 25.06.2016
comment
Спасибо обоим, посмотрим, исправит ли это переход на 3.5.1/2 или 3.6.0a2.   -  person jonrsharpe    schedule 25.06.2016
comment
Возможно, связано с: bugs.python.org/issue27243   -  person jonrsharpe    schedule 25.06.2016
comment
@MikeMüller Я все еще не мог заставить 3.5.1 работать с исходным кодом, мне пришлось добавить async, как показано ниже...   -  person jonrsharpe    schedule 25.06.2016
comment
@PadraicCunningham вышеперечисленное начнет работать в 3.5.2 (завтра!) И 3.6.x, только первые два выпуска исправлений 3.5.x не работают ...   -  person jonrsharpe    schedule 25.06.2016
comment
У меня это работало с Python 3.5.1, потому что у меня вообще не было установлено pytest-asyncio. ;). По крайней мере, это заставило вас смотреть на неправильный путь поиска версии. :) После установки pytest-asyncio могу воспроизвести вашу проблему и ваше решение.   -  person Mike Müller    schedule 26.06.2016


Ответы (2)


Если вы прочитаете немного дальше документацию упоминается, что (выделено мной):

PEP 492 был принят в CPython 3.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

Это было введено при закрытии ошибки #27243 и немного понятнее в документация по модели данных, в которой также предлагается способ написания обратно совместимого кода.

person jonrsharpe    schedule 25.06.2016

Асинхронные итераторы были реализованы в Python 3.6 — см. PEP-525.

Тогда вам вообще не нужна TestImplementation, чтобы использовать async for. Вы можете просто использовать 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}')
person KevinG    schedule 15.04.2019
comment
Это генератор, который не то же самое, что итератор. Не все итераторы можно преобразовать в генераторы, да и не следует. Суть вопроса OP заключалась в том, чтобы создать итератор, а не тестировать цикл async for. - person Philip Couling; 16.09.2020