asyncio awaitable object - базовый пример

Я пытаюсь понять, как сделать ожидаемый объект. В определении из документации указано:

Объект с методом __await__, возвращающий итератор.

Руководствуясь этим определением, я написал пример кода:

import asyncio

async def produce_list():
        num = await Customer()
        print(num)

class Customer(object):

    def __await__(self):
        return iter([1, 2, 3, 4])

loop = asyncio.get_event_loop()
loop.run_until_complete(produce_list())

Поток, который я ожидал, был:

  1. produce_list() события дает управление для produce_list(). produce_list() выполнение при num = await Customer().
  2. Customer() выполняется и возвращает итератор. Какой из них возвращает первое значение в итераторе. Q1: неясно, почему num не становится самим итератором. Также, что делает send здесь?
  3. Как только последнее значение достигнет итератора. num = 4 выполнение сопрограммы продолжается print(num) и печатает значение 4.

Что я имею:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
~/workspace/dashboard/so_question_await.py in <module>()
     16 
     17 loop = asyncio.get_event_loop()
---> 18 loop.run_until_complete(produce_list())

/usr/lib/python3.5/asyncio/base_events.py in run_until_complete(self, future)
    464             raise RuntimeError('Event loop stopped before Future completed.')
    465 
--> 466         return future.result()
    467 
    468     def stop(self):

/usr/lib/python3.5/asyncio/futures.py in result(self)
    291             self._tb_logger = None
    292         if self._exception is not None:
--> 293             raise self._exception
    294         return self._result
    295 

/usr/lib/python3.5/asyncio/tasks.py in _step(***failed resolving arguments***)
    239                 result = coro.send(None)
    240             else:
--> 241                 result = coro.throw(exc)
    242         except StopIteration as exc:
    243             self.set_result(exc.value)

~/workspace/dashboard/so_question_await.py in produce_list()
      5 
      6 async def produce_list():
----> 7         num = await Customer()
      8         print(num)
      9 

RuntimeError: Task got bad yield: 1

Какие понятия я получил здесь неправильно?

В конце я ищу пример, который использует итерацию через список как событие, чтобы вернуться к управлению сопрограммой.

Ответ 1

__await__ возвращает итератор, потому что основной механизм для сопрограмм первоначально основан на yield from синтаксиса. На практике __await__ возвращает либо iter(some_future) либо some_coroutine.__await__(). Он может использоваться для создания объектов, которые производят разные значения каждый раз, когда они ожидаются. См. Этот простой пример:

import asyncio
import random

class RandomProducer:

    def __await__(self):
        return self.producer().__await__()

    async def producer(self):
        sleep = random.random()
        value = random.randint(0, 9)
        return await asyncio.sleep(sleep, result=value)

async def main():
    producer = RandomProducer()
    while True:
        print(await producer)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Чтобы ответить на ваши комментарии:

В итоге каждая сопрограмма заканчивается вызовом asyncio.sleep?

Нет, и asyncio.sleep на самом деле не конец цепи. В самом низу всегда дается будущее: цепочка coroutine задает цикл событий "пожалуйста, разбудите меня, когда это будущее будет иметь результат". В случае asyncio.sleep, он использует loop.call_later чтобы установить результат будущего через определенное количество времени. Цикл предоставляет больше методов для планирования обратных вызовов: loop.call_at, loop.add_reader, loop.add_writer, loop.add_signal_handler и т.д.

Асинхронная библиотека, такая как aiohttp. Я предполагаю, что где-то есть код, который не полагается на существование предыдущих сопрограмм.

Все операции ввода-вывода должны завершиться делегированием цикла событий для достижения однопоточного параллелизма. Например, aiohttp полагается на loop.create_connection coroutine для управления TCP-соединением.