Точная спецификация __await__
Справочник по языку 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__» . В настоящее время он находится на рассмотрении.
Ответы
Язык не заботится , который итератор вы вернетесь. Ошибка исходит от библиотеки asyncio, в которой есть конкретные представления о типах значений, которые должен генерировать итератор. Asyncio требует __await__создания фьючерсов asyncio (включая их подтипы, такие как задачи) или None. Другие библиотеки, такие как curio и trio, ожидают других типов значений. Асинхронные библиотеки в целом не документируют свои ожидания, __await__потому что считают это деталью реализации.
Что касается asyncio, вы должны использовать конструкции более высокого уровня, такие как фьючерсы и задачи, и ожидать их в дополнение к сопрограммам. Существует редко возникает необходимость осуществить __await__вручную, и даже тогда вы должны использовать его , чтобы передать сигналы другого awaitable. Написание, __await__которое производит и дает свое собственное значение, требует, чтобы он был связан с циклом событий и имел представление о его внутреннем устройстве.
Вы можете думать об этом __await__как об инструменте для написания библиотеки, подобной asyncio. Если вы являетесь автором такой библиотеки, текущей спецификации достаточно, потому что вы можете получить все, что захотите, из итератора, только код в вашем цикле событий будет наблюдать полученные значения. Если вы не в таком положении, вероятно, у вас нет бизнеса по внедрению __await__.
Задачи могут ждать только другие задачи / фьючерсы. Из исходного кода 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__, хотя дело, вероятно, в том, что возвращаемый итерируемый возвращается в цикл событий, а затем в конечном итоге возвращает результат.