Точная спецификация __await__

Sep 18 2020

Справочник по языку Python определяет object.__await__следующее:

object.__await__(self)

Должен возвращать итератор. Следует использовать для реализации ожидаемых объектов. Например, asyncio.Futureреализует этот метод для совместимости с выражением ожидания.

Вот и все. Я считаю эту спецификацию очень расплывчатой ​​и не очень конкретной (по иронии судьбы). Хорошо, он должен вернуть итератор, но может ли это быть произвольный итератор? Очевидно нет:

import asyncio


class Spam:
    def __await__(self):
        yield from range(10)


async def main():
    await Spam()


asyncio.run(main())
RuntimeError: Task got bad yield: 0

Я предполагаю, что asyncioцикл обработки событий ожидает, что итератор выдаст определенный тип объекта. Тогда что именно он должен дать? (И почему это не задокументировано?)


Изменить: насколько я вижу, это нигде не задокументировано. Но я исследовал самостоятельно и думаю, что ключ к пониманию того, какие объекты asyncioожидает выдача сопрограмм, лежит task_step_implв _asynciomodule.c.


Обновление: я сделал PR в репозиторий cpython с целью прояснить это: «Уточните расплывчатую спецификацию object.__await__» . В настоящее время он находится на рассмотрении.

Ответы

7 user4815162342 Sep 19 2020 at 07:15

Язык не заботится , который итератор вы вернетесь. Ошибка исходит от библиотеки asyncio, в которой есть конкретные представления о типах значений, которые должен генерировать итератор. Asyncio требует __await__создания фьючерсов asyncio (включая их подтипы, такие как задачи) или None. Другие библиотеки, такие как curio и trio, ожидают других типов значений. Асинхронные библиотеки в целом не документируют свои ожидания, __await__потому что считают это деталью реализации.

Что касается asyncio, вы должны использовать конструкции более высокого уровня, такие как фьючерсы и задачи, и ожидать их в дополнение к сопрограммам. Существует редко возникает необходимость осуществить __await__вручную, и даже тогда вы должны использовать его , чтобы передать сигналы другого awaitable. Написание, __await__которое производит и дает свое собственное значение, требует, чтобы он был связан с циклом событий и имел представление о его внутреннем устройстве.

Вы можете думать об этом __await__как об инструменте для написания библиотеки, подобной asyncio. Если вы являетесь автором такой библиотеки, текущей спецификации достаточно, потому что вы можете получить все, что захотите, из итератора, только код в вашем цикле событий будет наблюдать полученные значения. Если вы не в таком положении, вероятно, у вас нет бизнеса по внедрению __await__.

1 uanirudhx Sep 19 2020 at 02:16

Задачи могут ждать только другие задачи / фьючерсы. Из исходного кода CPython :

    /* Check if `result` is FutureObj or TaskObj (and not a subclass) */
    /* ... */

    /* Check if `result` is None */
    /* ... error */

    /* Check if `result` is a Future-compatible object */
    /* ... */

    /* Check if `result` is a generator */
    /* ... */

    /* The `result` is none of the above */
    o = task_set_error_soon(
        task, PyExc_RuntimeError, "Task got bad yield: %R", result);
    Py_DECREF(result);
    return o;

Изменить: если я правильно понимаю, это ограничение налагается только на задачи, и нормальные фьючерсы могут ждать любого итерируемого, возвращаемого из __await__, хотя дело, вероятно, в том, что возвращаемый итерируемый возвращается в цикл событий, а затем в конечном итоге возвращает результат.