Тихо и незаметно (с), вышел 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

Проголосовал 251 человек. Воздержалось 176 человек.

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

Комментарии (26)


  1. baldr
    18.09.2015 17:41

    А как обстоит дело с потокобезопасностью в вашем примере? Например, объект 'current' — я правильно понимаю что вы просто опустили синхронизацию для упрощения?


    1. Alesh
      18.09.2015 17:48
      +6

      Никак, все происходит в одном потоке. Прелесть сопрограмм в том что они хорошо реализуют событийно-управляемую многозадачность (вытесняемую многозадачность) в одном потоке. Синхронизации нет, так как нечего синхронизировать, current нужно для отслеживания текущей (выполняющейся в данный момент) сопрограммы.


      1. RusSuckOFF
        18.09.2015 18:48

        Вытесняемая многозадачность, события и один поток в одном предложении, как мне кажется, образуют какую-то кашу. Под вытеснением обычно же понимают тот факт, что поток исполнения может переключится не по своему желанию. В этом же случае если сопрограмма написана криво, то она захватит поток исполнения навечно, разве нет?


        1. Alesh
          18.09.2015 19:03

          Безусловно, в скобочках должно быть написано (кооперативная многозадачность). Размышлял когда писал пост, почему оппоненту показалось что в примере задействованы потоки и выдал другое название обычной многопоточной многозадачности.


      1. kekekeks
        18.09.2015 21:49
        +4

        Вообще говоря это не вытесняющая, а кооперативная многозадачность. Есть event-loop, который шедулит сопрограммы. Ситуации, когда посреди выполнения синхронного кода (без await) будет переключен контекст на другую сопрограмму случиться не может.


        1. Alesh
          19.09.2015 13:34

          Вообще-то постом выше и раньше вашего я признал свою ошибку и даже попытался ее объяснить.


        1. zaz600
          20.09.2015 21:07

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


          1. Alesh
            20.09.2015 21:29

            1. Да запуск диспетчера событий, это последнее что запускается в основном потоке. Исключение может быть только что-то, что должно выполнится перед завершением программы.
            2. Запускать ее по таймеру и последующим перезапуском, точно так же как в примере.
            3. Сканировать каталоги можно, но придется периодически отдавать выполнение в другие сопрограммы (скажем уходить в ожидание таймаута (sleep), как в примере).
            4. Основной usecase наверно однопоточный сетевой сервис, у которого быстро формируется ответ. Но никто не мешает запускать несколько потоков и обменом между ними сообщениями. В этом случае usecase уже можно сильно расширить.



            1. zaz600
              20.09.2015 21:31

              спасибо


  1. Qualab
    18.09.2015 18:35
    +1

    Хорошая статья и хороший подход с async/await. Python как всегда всё больше радует с каждой версией.


  1. veveve
    19.09.2015 09:19
    +1

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

    В том-то и вся прелесть реализации асинхронности Python: вместо лапши из callback’ов, вы пишете обычный «синхронный» код, лишь изредка добавляя «async» и «await» в местах блокировок I/O, и получаете полностью асинхронную программу.


    1. Alesh
      19.09.2015 13:33
      +2

      Все верно, хочу лишь заметить, что все предыдущие реализации coroutine в Pythone, будь то реализация на генераторах в asyncio и tornado, etc или реализация на greenlet тоже позволяли писать обычный «синхронный» код без коллбеков, и даже без изредкого добавления «async» и «await».


  1. vovanbo
    22.09.2015 19:28

    swith_to -> switch_to


  1. niamster
    23.09.2015 02:46

    Если честно, то конструкция 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`?
    Также я заметил, что порядок выполнения разный, хотя все стадии проходит в обоих случаях. Если честно, то не совсем понятно почему так получается.


    1. Alesh
      23.09.2015 10:47

      Не обижайтесь, но вы либо совсем ничего не поняли, либо ваш опыт использования кода на 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 со своим простейшим диспетчером событий. Но это если интересно напишите, отдельным постом сделаю.


      1. niamster
        23.09.2015 15:22

        Спасибо за ответ.

        Не обижайтесь, но вы либо совсем ничего не поняли, либо ваш опыт использования кода на 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`, конечно. Возможно Вы сможете дать наглядный пример использования подобных объектов?

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


        1. Alesh
          24.09.2015 00:40

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

          Во втором примере самое трагическое это строка `time.sleep(self.timeout) # say hello` эта строка останавливает выполнение Python кода полностью. Соответственно ни о какой кооперативной многозадачности речи уже не идет.
          Я думаю смысла разбираться с тем нет, пока не разберемся с назначением сопрограммы. В примере с asyncio хотя бы диспетчер событий и awaitable объекты уже готовы.


  1. niamster
    24.09.2015 01:19

    Посмотрел на ваш следующий пример и еще раз вам пишу, вы используете сопрограммы в подходе как использовали бы функции обратного вызова. Зачем? Сформулируйте мне какую задачу реализовываете в примере. Я дам свой пример, возможно это поможет разобраться. А возможно задача у вас вообще не укладывающееся в 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())
    


    1. Alesh
      24.09.2015 11:22

      Почему я не могу использовать 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()
      



      1. Alesh
        24.09.2015 11:32

        Можно 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
        


        1. niamster
          24.09.2015 22:02

          Спасибо за развернутый ответ.

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

          Я так понял наше недопонимание возникло из-за этого самого furure-like object. Как я упоминал ранее — хотел на самом деле пощупать что оно такое. Исходя из ваших ответов, я не представляю что это за фрукт но все-таки хотелось бы понять суть awaitable объекта и почему по вашему мнению он здесь не клеится.


          1. Alesh
            24.09.2015 22:28

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


            1. niamster
              24.09.2015 22:58

              В том то и дело, что примеров нет.

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

              На вопрос зачем — а вдруг я не хочу тянуть весь asyncio просто потому, что хочу баловаться python на устройстве с ограниченными ресурсами.


              1. Alesh
                24.09.2015 23:18

                Осталось только услышать ответ эксперта по поводу использования голого async/await без сторонних библиотек

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

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

                Как писать диспетчер, я приводил пример в предыдущей статье, но в принципе тема event loop не имеет прямого отношения к coroutine ибо с таким же успехом используется в асинхронных программах построенных на функциях обратного вызова.


              1. Alesh
                24.09.2015 23:23

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


                1. niamster
                  24.09.2015 23:34

                  Надеюсь не потеряете. Тема для python интересная, а по сути кроме asyncio никакой «легковесной» альтернативы или примеров реализации в сети нет =/