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 методы асинхронного программирования.
    async/await как реализация асинхронного программирования в Python

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

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


        • +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 никакой «легковесной» альтернативы или примеров реализации в сети нет =/

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.