Python 3.5; async/await

    Тихо и незаметно (с), вышел Python версии 3.5! И, безусловно, одно из самых интересных нововведений релиза является новый синтаксис определения сопрограмм с помощью ключевых слов async/await, далее в статье об этом.

    Поверхностный просмотр «PEP 0492 — Coroutines with async and await syntax» по началу оставил у меня вопрос «Зачем это надо». Сопрограммы удовлетворительно реализуются на расширенных генераторах и на первый взгляд может показаться, что все свелось к замене yield from на await, а декоратора, создающего сопрограмму на async. Сюда можно добавить и возникающее ощущение, что все это сделано исключительно для использования с модулем asyncio.

    Но это, конечно же, не так, тема глубже и интереснее.

    coroutine

    Главное, наверное, это то, что теперь сопрограмма в Python — это специальный объект native coroutine, а не каким-то специальным образом оформленный генератор или еще что-то. Этот объект имеет методы и функции стандартной библиотеки для работы с ним. То есть теперь, это объект, определяемый как часть языка.

    await

    К сожалению, не нашел в документации и PEP краткое определение для чего введено это новое ключевое слово. Рискну сформулировать его сам: Ключевое слово await указывает, что при выполнении следующего за ним выражения возможно переключение с текущей сопрограммы на другую или на основной поток выполнения.
    Соответственно выражение после await тоже не простое, это должен быть awaitable объект.

    awaitable object

    Есть три варианта awaitable объектов:
    • Другая сопрограмма, а именно объект native coroutine. Этот напоминает, и видимо реализовано аналогично случаю, когда в генераторе с помощью yield from вызывается другой генератор.
    • Сопрограмма на основе генератора, созданная с помощью декоратора types.coroutine(). Это вариант обеспечения совместимости с наработками, где сопрограммы реализованы на основе генераторов.
    • Специальный объект, у которого реализован магический метод __await__, возвращающий итератор. С помощью этого итератора реализуется возврат результата выполнения сопрограммы.

    Примера, как написать свой awaitable объект ни в PEP, ни в документации не нашел, во всяком случае на момент написания статьи этого нет. Ниже этот недостаток будет исправлен.)

    async

    В РЕР десяток абзацев с заголовками «Why ...» и «Why not ...». Почти все они посвящены вопросу, почему это ключевое слово используется так, а не как-то иначе. И действительно, async def смотрится в коде странно, и вызывает размышления на тему а «pythonic way» ли это? С другой стороны, понятно, что хотели какой-то более целостной картины, так как есть еще и async for и async with.
    • async def — определяет native coroutine function, результатом вызова которой будет объект-сопрограмма native coroutine, пока еще не запущенная.
    • async for — определяет, что итератор используемый в цикле, при получении следующего значения может переключать выполнение с текущей сопрограммы. Объект итератор имеет вместо стандартных магических методов: __iter__ и __next__, методы: __aiter__ и __anext__. Функционально они аналогичны, но как следует из определения, допускают использования await в своем теле.
    • async with — определяет, что при входе в контекстный блок и выходе из него может быть переключение выполнения с текущей сопрограммы. Так же, как и в случае с асинхронным генератором, вместо магических методов: __enter__ и __exit__ следует использовать функционально аналогичные __aenter__ и __aexit__.

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

    Примеры на использование асинхронных итераторов и контекст менеджеров в документации и PEP достаточно, usecase в общем-то понятен и все логично. Непонятно только одно — зачем использовать версии магических методов с другими именами, ведь они все равно объявляются с использованием `async def`. Видимо, это что-то, связанное с особенностями реализации, другого объяснения не вижу.

    Как это готовить?


    Изучение какой-то новой фичи языка или библиотеки быстро упирается в вопрос, как и где это использовать. И более глубокое изучение, на мой взгляд, стоит продолжать уже на практическом примере. Для меня, если тема связана с сопрограммами, асинхронностью и тому подобными вещами, такой практический пример — это написание хеллоуворда, использующего event-driven подход. Формулировка задачи такая: «Вызов функции sleep должен остановить исполнение сопрограммы на определенное время».

    Сопрограммы и event-driven прекрасно сочетаются, в другой моей статье более подробно, почему я так считаю. И пример такого рода хорошо нам продемонстрирует почти все возможности и нюансы использования сопрограмм.

    Предположим, что мы имеем диспетчер событий, запущенный в основном потоке исполнения, который при возникновении ожидаемых событий вызывает функции обратного вызова. Тогда практическая реализация может быть такой:
    • Функция sleep настраивает диспетчер событий на вызов функции обратного вызова через заданный промежуток времени. После этого переключает управление в основной поток исполнения (то есть на диспетчер).
    • Переданная в диспетчер функция обратного вызова вызывается по истечении заданного времени. В ней переходит переключение на сопрограмму с передачей ей какой-то полезной информации.

    Закодировано это может быть как-то так:
    from time import time
    from collections import deque
    from tornado.ioloop import IOLoop
    
    current = deque()
    
    class sleep(object):
    
        def __init__(self, timeout):
            self.deadline = time() + timeout
    
        def __await__(self):
            def swith_to(coro):
                current.append(coro)
                coro.send(time())
            IOLoop.instance().add_timeout(self.deadline, swith_to, current[0])
            current.pop()
            return (yield)
    
    def coroutine_start(run, *args, **kwargs):
        coro = run(*args, **kwargs)
        current.append(coro)
        coro.send(None)
    
    if __name__ == '__main__':
    
        async def hello(name, timeout):
            while True:
                now = await sleep(timeout)
                print("Hello, {}!\tts: {}".format(name, now))
    
        coroutine_start(hello, "Friends", 1.0)
        coroutine_start(hello, "World", 2.5)
        IOLoop.instance().start()
    

    Как видите, код краткий, достаточно понятный и, кстати, рабочий. Опишу основные моменты в нем:
    1. В качестве диспетчера событий использован tornado.ioloop.IOLoop комментировать по моему тут особо нечего.
    2. Класс sleep — реализует awaitable объект, его функция — передать управление в диспетчер событий, предварительно настроив его на вызов callback через заданный промежуток времени.
    3. Функция обратного вызова определена как замыкание, но в данном случае это не играет никакой роли. Назначение ее — просто переключить выполнение назад на сопрограмму с передачей текущего времени. Переключение выполнения на сопрограмму, производится вызовом ее метода send или метода throw для переключения с выбросом исключения.
    4. Назначение функции coroutine_start — это создать сопрограмму, вызвав функцию фабрику и запустить ее на выполнение. Первый вызов метода send сопрограммы, обязательно должен быть с параметром None — это запускает сопрограмму
    5. Сама функция hello тривиальна. Может и так понятно, но думаю стоит уточнить. Эта функция не сопрограмма! Эта функция, которая создает и возвращает сопрограмму ( функция-фабрика), аналогично функциям, создающим и возвращающим генератор.


    Развитие этой идеи: «async/await coroutine and event-driven», можно посмотреть по этой ссылке. Оно еще сырое, но кроме продемонстрированного переключения по событию «timeout», реализовано переключение сопрограмм по событиям «I/O ready» и «system sygnal». В качестве демо, есть пример асинхронного echo server.

    В заключение


    Сопрограммы в том или ином виде были доступны уже достаточно давно, но не было «официальных правил игры с ними». Теперь эти «правила игры» определены и зафиксированы и это станет хорошим поводом более широко использовать в Python методы асинхронного программирования.

    Only registered users can participate in poll. Log in, please.

    async/await как реализация асинхронного программирования в Python

    Share post

    Comments 26

      0
      А как обстоит дело с потокобезопасностью в вашем примере? Например, объект 'current' — я правильно понимаю что вы просто опустили синхронизацию для упрощения?
        +6
        Никак, все происходит в одном потоке. Прелесть сопрограмм в том что они хорошо реализуют событийно-управляемую многозадачность (вытесняемую многозадачность) в одном потоке. Синхронизации нет, так как нечего синхронизировать, current нужно для отслеживания текущей (выполняющейся в данный момент) сопрограммы.
          0
          Вытесняемая многозадачность, события и один поток в одном предложении, как мне кажется, образуют какую-то кашу. Под вытеснением обычно же понимают тот факт, что поток исполнения может переключится не по своему желанию. В этом же случае если сопрограмма написана криво, то она захватит поток исполнения навечно, разве нет?
            0
            Безусловно, в скобочках должно быть написано (кооперативная многозадачность). Размышлял когда писал пост, почему оппоненту показалось что в примере задействованы потоки и выдал другое название обычной многопоточной многозадачности.
            +4
            Вообще говоря это не вытесняющая, а кооперативная многозадачность. Есть event-loop, который шедулит сопрограммы. Ситуации, когда посреди выполнения синхронного кода (без await) будет переключен контекст на другую сопрограмму случиться не может.
              0
              Вообще-то постом выше и раньше вашего я признал свою ошибку и даже попытался ее объяснить.
                0
                т.е. всегда необходимо запускать луп и на нем всегда выполнение основного кода завершается? в том смысле, что после него нет смысла чего-нибудь выполнять.
                как запускать сопрограмму выполняющую периодически какое-либо действие? например, сканирующую каталоги и обновляющую какой-нибудь массив, но при этом другая сопрограмма должна что-то с этим массивом делать?
                вообще, какое применение данной функциональности может быть, если все выполняется в одном потоке?:) ну, кроме tcp-клиента или ожидания расчета факториала:)
                  0
                  1. Да запуск диспетчера событий, это последнее что запускается в основном потоке. Исключение может быть только что-то, что должно выполнится перед завершением программы.
                  2. Запускать ее по таймеру и последующим перезапуском, точно так же как в примере.
                  3. Сканировать каталоги можно, но придется периодически отдавать выполнение в другие сопрограммы (скажем уходить в ожидание таймаута (sleep), как в примере).
                  4. Основной usecase наверно однопоточный сетевой сервис, у которого быстро формируется ответ. Но никто не мешает запускать несколько потоков и обменом между ними сообщениями. В этом случае usecase уже можно сильно расширить.


                    0
                    спасибо
            +1
            Хорошая статья и хороший подход с async/await. Python как всегда всё больше радует с каждой версией.
              +1
              >Использование термина «асинхронный код» может ввести в заблуждение, потому-что «асинхронный код» часто реализуется на функциях обратного вызова, а это немного другая тема.

              В том-то и вся прелесть реализации асинхронности Python: вместо лапши из callback’ов, вы пишете обычный «синхронный» код, лишь изредка добавляя «async» и «await» в местах блокировок I/O, и получаете полностью асинхронную программу.
                +2
                Все верно, хочу лишь заметить, что все предыдущие реализации coroutine в Pythone, будь то реализация на генераторах в asyncio и tornado, etc или реализация на greenlet тоже позволяли писать обычный «синхронный» код без коллбеков, и даже без изредкого добавления «async» и «await».
                0
                swith_to -> switch_to
                  0
                  Если честно, то конструкция await/async не слишком выразительна. Какая-то мешанина из обработчиков и генераторов.
                  На практике я python3 не использую, потому мои наблюдения могут не иметь смысла.

                  Попытался сочинить что-то идя по вашим стопам и докам в сети, получилось такое:
                  import asyncio
                  
                  async def hello(name, timeout):
                      await poke(name, timeout)
                  
                  class poke:
                      def __init__(self, name, timeout):
                          self.name = name
                          self.timeout = timeout
                  
                      def __await__(self):
                          yield from asyncio.sleep(self.timeout) # say hello
                          if not self.name.startswith("world"):
                              yield from asyncio.wait([hello("world.{}".format(self.name), 0.3)])
                          yield
                  
                  async def friends():
                      await asyncio.wait([
                          hello("friends", 0.5),
                          hello("neighbours", 0.3),
                      ])
                  
                  loop = asyncio.get_event_loop()
                  loop.run_until_complete(friends())
                  


                  Вроде все понятно. А теперь я решил включить мозги и попробовать без asyncio:
                  import time
                  
                  async def hello(name, timeout):
                      await poke(name, timeout)
                  
                  class poke:
                      def __init__(self, name, timeout):
                          self.name = name
                          self.timeout = timeout
                  
                      def __await__(self):
                          time.sleep(self.timeout) # say hello
                          if not self.name.startswith("world"):
                              coro = hello("world.{}".format(self.name), 0.3)
                              while True:
                                  try:
                                      coro.send(None)
                                      yield
                                  except StopIteration:
                                      break
                          yield
                  
                  async def friends():
                      coros = [
                          hello("friends", 0.5),
                          hello("neighbours", 0.3)
                      ]
                      for coro in coros:
                          coro.send(None)
                      for coro in coros:
                          await coro
                  
                  poll = friends()
                  while True:
                      try:
                          poll.send(None)
                      except StopIteration:
                          break
                  


                  Второй вариант выглядит коряво и асимметрично. В чем дело? Так и должно быть?
                  Почему нельзя вызывать `await` в `__await__` методе? Почему для `friends` я также не могу вызвать `await`, но должен в цикле слать `send`?
                  Также я заметил, что порядок выполнения разный, хотя все стадии проходит в обоих случаях. Если честно, то не совсем понятно почему так получается.
                    0
                    Не обижайтесь, но вы либо совсем ничего не поняли, либо ваш опыт использования кода на callback, был категорически против загрузки в голову :) материала про coroutine. Видно четко, что вы не поняли преимуществ, которые дает использование сопрограмм и пытались притянуть в примеры концепцию функций обратного вызова. Я сейчас прокомментирую ваш код с asyncio и дам свой вариант.

                    • Сопрограмма созданная вызовом hello у вас завершается не успев сделать чего-то существенного, кроме ожидания своего завершения.
                    • Зачем вы создаете и запускаете другую сопрограмму в await объекте?
                    • Добавление «world.», может я не понял идеи. Если это для ограничение итераций, то подход абсолютно неверный — в самой короутине это можно и нужно сделать.
                    • Функции asyncio такие как sleep, wait, etc. уже возвращают awaitable объект. Использовать с ними yield from не надо.
                    • def friends() — это просто неоправданное увеличение энтропии :)


                    import asyncio
                    
                    async def hello(name, timeout):
                        cnt = 0
                        while True and cnt < 5:
                            await asyncio.sleep(timeout)
                            print("Hello, {}".format(name))
                            cnt += 1
                    
                    if __name__ == '__main__':
                    
                        tasks = [
                            hello("friends", 0.5),
                            hello("neighbours", 0.3),
                        ]
                    
                        loop = asyncio.get_event_loop()
                        loop.run_until_complete(asyncio.wait(tasks))
                        loop.close()
                    


                    В втором варианте, к проблемам первого еще и добавляется event-driven. Вам надо почитать больше про событийное программирование. Предыдущая моя статья немного этого касалась. А как пример, ну я уже приводил пример в статье без asyncio. Единственно могу сделать пример без tornado loop со своим простейшим диспетчером событий. Но это если интересно напишите, отдельным постом сделаю.
                      0
                      Спасибо за ответ.
                      Не обижайтесь, но вы либо совсем ничего не поняли, либо ваш опыт использования кода на callback, был категорически против загрузки в голову :) материала про coroutine. Видно четко, что вы не поняли преимуществ, которые дает использование сопрограмм и пытались притянуть в примеры концепцию функций обратного вызова.

                      Я не обижаюсь, а пытаюсь разгрести кашу в голове. С coroutine я знаком — libevent в C, Fiber в ruby — в данных случаях у меня не возникало проблем с пониманием. В вашем примере сложно отследить что за чем следует. Да, если использовать coroutine_start и закрыть глаза на все остальное — ничего сложного. К стати не совсем понятно зачем `current.append(coro)` в `switch_to` — чтоб поддерживать бесконечный цикл в `hello`? Зачем там вообще бесконечный цикл? Если убрать бесконечный цикл, то обьект sleep не будет функционировать правильно(список current всегда будет содержать лишние `hello`) и, если я правильно понимаю, в определенный момент выскочит исключение.

                      В своем примере я попытался опробовать coroutine generator и вложенные вызовы этих же coroutine.
                      Сопрограмма созданная вызовом hello у вас завершается не успев сделать чего-то существенного, кроме ожидания своего завершения.

                      Ну как же — запускает coroutine `poke`. Да, не возвращает результат, но это пример сказать «привет» — считайте keep-alive.
                      Зачем вы создаете и запускаете другую сопрограмму в await объекте?

                      Возможно пример получился неудачный, но в данном случае я хотел просто вызвать еще один await, почему бы и нет?
                      Добавление «world.», может я не понял идеи. Если это для ограничение итераций, то подход абсолютно неверный — в самой короутине это можно и нужно сделать.

                      В данном случае чтоб избежать бесконечной рекурсии. Да, лучше бы вызвал не hello, а что-то другое.
                      Функции asyncio такие как sleep, wait, etc. уже возвращают awaitable объект. Использовать с ними yield from не надо.

                      Если я правильно понимаю, то в `__await__` можно использовать либо `yield from` либо `return`. Python3.5 не разрешает использовать `await` в `__await__`
                      def friends() — это просто неоправданное увеличение энтропии :)

                      Не совсем понятно данное утверждение.

                      Попытался соорудить более понятный пример, в котором я приглашаю друзей на «вечеринку»:
                      import asyncio
                      
                      async def say(name, what, timeout):
                          return await poke('{} {}'.format(what, name), timeout)
                      
                      async def ping(timeout):
                          await asyncio.sleep(timeout)
                          return 'OK'
                      
                      async def handshake(timeout):
                          await asyncio.sleep(timeout)
                          return 'OK'
                      
                      class poke:
                          def __init__(self, name, timeout):
                              self.name = name
                              self.timeout = timeout
                      
                          def __await__(self):
                              res = yield from asyncio.wait_for(ping(0.1), None)
                              assert res == 'OK'
                              res = yield from asyncio.wait_for(handshake(0.1), None)
                              assert res == 'OK'
                              return 'OK'
                      
                      async def invite_friends():
                          res, _ = await asyncio.wait([
                              say("friends", 'hello', 0.5),
                              say("neighbours", 'hello', 0.3),
                          ])
                          assert all(([x.result() == 'OK' for x in res]))
                      
                      loop = asyncio.get_event_loop()
                      loop.run_until_complete(invite_friends())
                      


                      Возможно я не понимаю основное идеи Future-like объектов? В моем случае можно было бы и обойтись `async coroutine`, конечно. Возможно Вы сможете дать наглядный пример использования подобных объектов?

                      Также все еще интересно увидеть комментарии ко второй части моего оригинального поста.
                        0
                        Посмотрел на ваш следующий пример и еще раз вам пишу, вы используете сопрограммы в подходе как использовали бы функции обратного вызова. Зачем? Сформулируйте мне какую задачу реализовываете в примере. Я дам свой пример, возможно это поможет разобраться. А возможно задача у вас вообще не укладывающееся в async/awaite, и пытаясь ее все же решить через coroutine вы приходите к таким странным вещам как сопрограммы делающие только то, что дожидающиеся своего завершения.
                        `current.append(coro)` — `current[0]` Будет содержать текущую выполняющуюся сопрограмму. В том примере это не совсем нужно, но если бы был запуск другой короутины из исполняющейся без этого было бы не обойтись.
                        `switch_to` — не поддерживает цикл, а возвращает управление в короутину.

                        Во втором примере самое трагическое это строка `time.sleep(self.timeout) # say hello` эта строка останавливает выполнение Python кода полностью. Соответственно ни о какой кооперативной многозадачности речи уже не идет.
                        Я думаю смысла разбираться с тем нет, пока не разберемся с назначением сопрограммы. В примере с asyncio хотя бы диспетчер событий и awaitable объекты уже готовы.
                    0
                    Посмотрел на ваш следующий пример и еще раз вам пишу, вы используете сопрограммы в подходе как использовали бы функции обратного вызова. Зачем? Сформулируйте мне какую задачу реализовываете в примере. Я дам свой пример, возможно это поможет разобраться. А возможно задача у вас вообще не укладывающееся в async/awaite, и пытаясь ее все же решить через coroutine вы приходите к таким странным вещам как сопрограммы делающие только то, что дожидающиеся своего завершения.

                    Замените asyncio.sleep на что-то вроде loop.sock_sendall+loop.sock_recv. Так лучше понятна задача? Использовал asyncio.sleep для эмуляции задержек в качестве примера.
                    Задача такова — есть список друзей, нужно всех пригласить на праздник. Чтоб пригласить на праздник сначала нужно дозвониться(ping), потом уговорить прийти(handshake). Представим, что я могу звонить всем одновременно, и пока один тупит — могу говорить с другим. Пока говорю с одним — все ждут. Собственно под этим я и понимаю coroutines в single-threaded event loop.

                    Почему я не могу использовать coroutines в данном примере? Как бы вы реализовали эту задачу?

                    Возможно необходимо было использовать `s/say/invite/`чтоб было более очевидно, согласен.
                    Ради наглядности переписал бы как-то так:
                    import asyncio
                    import logging
                    from random import random
                    
                    logging.getLogger().setLevel(logging.DEBUG)
                    
                    class dialog:
                        def __init__(self, name, latency):
                            self.name = name
                            self.latency = latency
                    
                        async def call(self):
                            logging.debug('calling {}'.format(self.name))
                            await asyncio.sleep(self.latency/2+random())
                            return 'OK'
                    
                        async def convince(self):
                            logging.debug('convincing {}'.format(self.name))
                            await asyncio.sleep(self.latency/2+random())
                            return 'OK'
                    
                        def __await__(self):
                            res = yield from asyncio.wait_for(self.call(), None)
                            assert res == 'OK'
                            res = yield from asyncio.wait_for(self.convince(), None)
                            assert res == 'OK'
                            logging.debug('invited {}'.format(self.name))
                            return 'OK'
                    
                    async def invite(name, latency):
                        return await dialog(name, latency)
                    
                    async def invite_friends():
                        friends = [
                            # (name, latency)
                            ('mark', 0.5),
                            ('bob', 0.3),
                        ]
                        coros = [invite(name, latency) for name, latency in friends]
                        res, _ = await asyncio.wait(coros)
                        assert all(([x.result() == 'OK' for x in res]))
                    
                    loop = asyncio.get_event_loop()
                    loop.run_until_complete(invite_friends())
                    

                      0
                      Почему я не могу использовать coroutines в данном примере?
                      Я не говорил, что в этой задачи нельзя использовать coroutine, не внимательно прочитали мой ответ?
                      Как бы вы реализовали эту задачу?
                      Задачу вы описали, я ее понял, и мой пример будет ниже. Теперь мне и стало понятно что у вас не так. Зачем делать логику, тем более прикладную в awaitable объекте? Он для этого не предназначен. Делайте всю логику в сопрограммах. Пример ниже, возможно многословный, но просто хотелось красивый лог:

                      import random
                      import logging
                      import asyncio
                      
                      async def call_to(name):
                          cnt = 0
                          max_ring = 7
                          result = False
                          logging.debug("Calling {} ...".format(name))
                          attempts = random.randrange(0, 9, 1) + 1
                      
                          while cnt < attempts:
                              await asyncio.sleep(1.0)
                              logging.debug("({}): beep".format(name))
                              cnt += 1
                              if cnt == max_ring:
                                  logging.debug("({}): not picked up".format(name))
                                  break
                          else:
                              result = True
                          return result
                      
                      
                      async def sell_on(name):
                          cnt = 0
                          max_offer = 3
                          logging.debug("Responding {} ...".format(name))
                          while True:
                              cnt += 1
                              await asyncio.sleep(1.0)
                              answer = random.randrange(0, 3, 1)
                              if answer == 2:
                                  logging.debug("({}): Yes, I will come".format(name))
                                  return True
                              elif  answer == 1:
                                  logging.debug("({}): No, I will not come".format(name))
                                  return False
                              else:
                                  if cnt == max_offer:
                                      logging.debug("({}): No, I will not come".format(name))
                                      return False
                                  else:
                                      logging.debug("({}): Maybe, I don't know".format(name))
                      
                      
                      async def invite(name, result):
                          answered = await call_to(name)
                          if answered:
                              agreed = await sell_on(name)
                              result.append((name, agreed))
                          else:
                              result.append((name, answered))
                      
                      
                      if __name__ == '__main__':
                      
                          logging.basicConfig(level=logging.DEBUG)
                      
                          result = list()
                          frends = ['Саша', 'Паша', 'Катя', 'Маша', 'Дуся', 'Маруся', 'Ваня']
                          tasks = [invite(name, result) for name in frends]
                      
                          loop = asyncio.get_event_loop()
                          loop.run_until_complete(asyncio.wait(tasks))
                      
                          print("\n----------------------------------------")
                          for name, agreed in result:
                              print("{}\t{}".format(name, "придет" if agreed else "не придет"))
                      
                          loop.close()
                      


                        0
                        Можно sell_on переписать более интересно:

                        async def sell_on(name):
                            cnt = 0
                            max_offer = 3
                            logging.debug("Responding {} ...".format(name))
                            while True:
                                cnt += 1
                                answer = random.randrange(0, 9, 1) + 1
                                await asyncio.sleep(answer)
                                if answer % 2:
                                    logging.debug("({}): Yes, I will come".format(name))
                                    return True
                                else:
                                    logging.debug("({}): No, I will not come".format(name))
                                    return False
                        
                          0
                          Спасибо за развернутый ответ.

                          Грубо говоря тоже самое, что и в моем примере, только без future-like object.

                          Я так понял наше недопонимание возникло из-за этого самого furure-like object. Как я упоминал ранее — хотел на самом деле пощупать что оно такое. Исходя из ваших ответов, я не представляю что это за фрукт но все-таки хотелось бы понять суть awaitable объекта и почему по вашему мнению он здесь не клеится.
                            0
                            Мне кажется, ну собственно и в документации awaitable объект упоминается и применяется только качестве, специализированного объекта переключающего управление с/на сопрограмму с возвратом или нет результата выполнения асинхронного действия. Во всяком случае примеров нет, которые бы более полно или както по другому раскрыли его назначение.
                            Зачем нагружать его еще какой-то логикой, если это удобнее и логичнее сделать в суб. сопрограмме вызываемой из текущей сопрограммы с помощью ключевого слова await. Здесь логика похожа на вложенные генераторы, вызываемые с помощью yield from, вернее не похоже, а по внутренней реализации тоже самое.
                              0
                              В том то и дело, что примеров нет.

                              Осталось только услышать ответ эксперта по поводу использования голого async/await без сторонних библиотек типа tornado(ok, asyncio встроен в python, но он же не реализует все на свете). Собственно меня смущает как coroutine запускается(см. второй кусок кода из моего первого комментария). С time.sleep понятно(хотя не логично — могли бы допилить), но вызов в цикле который прерывается по исключению StopIteration — как по мне либо выглядит убого.

                              На вопрос зачем — а вдруг я не хочу тянуть весь asyncio просто потому, что хочу баловаться python на устройстве с ограниченными ресурсами.
                                0
                                Осталось только услышать ответ эксперта по поводу использования голого async/await без сторонних библиотек

                                Окей) Что нам дает asyncio, из того что мы использовали в своих примерах? Оно нам дает event loop (диспетчер событий) и функцию sleep, которая возвращает awaitable. Этот awaitable делает следующее, переключает управление на event loop предварительно каким-то образом наладив event loop на возврат управления в текущую сопрограмму через заданный промежуток времени.

                                В примере, в статье, я использовал event loop tornado, а awaitable объект написал сам. Можем его разобрать если не вполне понятно, что там происходит.

                                Как писать диспетчер, я приводил пример в предыдущей статье, но в принципе тема event loop не имеет прямого отношения к coroutine ибо с таким же успехом используется в асинхронных программах построенных на функциях обратного вызова.
                                  0
                                  Насчет «не хочу тянуть весь asyncio», да из него слепили монстра на все случаи жизни. Хотя в большинстве своем достаточно event loop и трех awaitable объектов типо sleep, wait_io, wait_signal. Я как раз собираюсь исправить этот недостаток asyncio :) если не потеряю интерес и мотивацию.
                                    0
                                    Надеюсь не потеряете. Тема для python интересная, а по сути кроме asyncio никакой «легковесной» альтернативы или примеров реализации в сети нет =/

                      Only users with full accounts can post comments. Log in, please.