Запустить функцию async без импорта пакета asyncio

Можно ли запустить такую ​​функцию

async def foo():
    while True:
        print("Hello!")

без импорта пакета asyncio (и получения цикла событий)?

Я ищу принцип, похожий на горутины Go, где можно запустить сопрограмму с помощью только оператора go.

Изменить: причина, по которой я не импортирую пакет asyncio, просто потому, что я думаю, что должна быть возможность запускать сопрограмму без цикла событий (явно). Я не понимаю, почему async def и подобные операторы являются частью основного языка (даже частью синтаксиса), а способ запуска созданных сопрограмм доступен только через package.


person J-qak    schedule 23.02.2016    source источник
comment
Что касается настоящих сопрограмм для Python 3.5, я в этом сомневаюсь, потому что что-то должно, так сказать, крутить колесо. Хотя вы можете попробовать реализовать сопрограмму «старомодным» способом, создав генератор python и скармливая его операторами send().   -  person songololo    schedule 24.02.2016
comment
Почему вы не хотите импортировать asyncio?   -  person dirn    schedule 24.02.2016


Ответы (4)


Конечно, можно запустить функцию async без явного использования asyncio. В конце концов, asyncio написан на Python, так что все, что он делает, вы тоже можете (хотя иногда вам могут понадобиться другие модули, такие как selectors или threading, если вы собираетесь одновременно ждать внешних событий или параллельно выполнять какой-то другой код).

В этом случае, поскольку ваша функция не имеет await точек внутри, ей достаточно одного нажатия, чтобы начать работу. Вы вставляете сопрограмму, send вставляя None в нее.

>>> foo().send(None)
Hello!
Hello!
...

Конечно, если ваша функция (сопрограмма) имеет yield выражений внутри, она приостанавливает выполнение в каждой yield точке, и вам нужно будет вставить в нее дополнительные значения (coro.send(value) или next(gen)), но вы уже знаете это, если знаете, как генераторы Работа.

import types

@types.coroutine
def bar():
    to_print = yield 'What should I print?'
    print('Result is', to_print)
    to_return = yield 'And what should I return?'
    return to_return

>>> b = bar()
>>> next(b)
'What should I print?'
>>> b.send('Whatever you want')
Result is Whatever you want
'And what should I return?'
>>> b.send(85)
Traceback...
StopIteration: 85

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

async def baz():
    first_bar, second_bar = bar(), bar()
    print('Sum of two bars is', await first_bar + await second_bar)
    return 'nothing important'

>>> t = baz()
>>> t.send(None)
'What should I print?'
>>> t.send('something')
Result is something
'And what should I return?'
>>> t.send(35)
'What should I print?'
>>> t.send('something else')
Result is something else
'And what should I return?'
>>> t.send(21)
Sum of two bars is 56
Traceback...
StopIteration: nothing important

Теперь все эти .send начинают утомлять. Было бы неплохо, если бы они генерировались полуавтоматически.

import random, string

def run_until_complete(t):
    prompt = t.send(None)
    try:
        while True:
            if prompt == 'What should I print?':
                prompt = t.send(random.choice(string.ascii_uppercase))
            elif prompt == 'And what should I return?':
                prompt = t.send(random.randint(10, 50))
            else:
                raise ValueError(prompt)
    except StopIteration as exc:
        print(t.__name__, 'returned', exc.value)
        t.close()

>>> run_until_complete(baz())
Result is B
Result is M
Sum of two bars is 56
baz returned nothing important

Поздравляем, вы только что написали свой первый цикл обработки событий! (Не ожидал, что это произойдет, не так ли ?;) Конечно, он ужасно примитивен: он знает только, как обрабатывать два типа подсказок, он не позволяет t создавать дополнительные сопрограммы, которые выполняются одновременно с ним, и он подделывает события с помощью генератора random.

(На самом деле, если вы хотите пофилософствовать: то, что мы делали выше вручную, можно также назвать циклом событий: Python REPL выводил приглашения в окно консоли, и он полагался на вас предоставьте события, набрав в нем t.send(whatever). :)

asyncio - это просто чрезвычайно обобщенный вариант вышеизложенного: подсказки заменены на Future, несколько сопрограмм хранятся в очередях, поэтому каждая из них в конечном итоге получает свою очередь, а события намного богаче и включают связь по сети / сокету, чтение / запись файловой системы, сигнал обработка, выполнение потока / процесса на стороне и т. д. Но основная идея все та же: вы берете несколько сопрограмм, жонглируете ими в воздухе, направляя фьючерсы от одного к другому, пока все они не поднимут StopIteration. Когда всем сопрограммам нечего делать, вы отправляетесь во внешний мир и захватываете некоторые дополнительные события, которые они могут пережить, и продолжаете.

Надеюсь, теперь все стало намного яснее. :-)

person Veky    schedule 28.07.2016
comment
Замечательно, не могу дождаться, чтобы проверить это! - person J-qak; 01.08.2016
comment
Если у вас есть еще вопросы, просто задавайте их. Буду рад ответить. - person Veky; 03.08.2016

Сопрограммы Python - это синтаксический сахар для генераторов с некоторыми дополнительными ограничениями в их поведении (так что их назначение явно отличается и не смешивается). Вы не можете:

next(foo())
TypeError: 'coroutine' object is not an iterator

потому что он отключен явно. Однако вы можете:

foo().send(None)
Hello
Hello
Hello
...

Что эквивалентно next() для генератора.

person Zah    schedule 28.05.2016

Сопрограммы должны уметь

  1. запустить

  2. передать управление вызывающему абоненту (при желании можно получить промежуточные результаты)

  3. иметь возможность получить некоторую информацию от звонящего и возобновить

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

#!/usr/bin/env python

import types

# two simple async functions
async def outer_af(x):
    print("- start outer_af({})".format(x))
    val = await inner_af(x)  # Normal way to call native coroutine.
                             # Without `await` keyword it wouldn't
                             # actually start
    print("- inner_af result: {}".format(val))
    return "outer_af_result"


async def inner_af(x):
    print("-- start inner_af({})".format(x))
    val = await receiver()  # 'await' can be used not only with native
                            # coroutines, but also with `generator-based`
                            # coroutines!
    print("-- received val {}".format(val))
    return "inner_af_result"


# To yiled execution control to caller it's necessary to use
# 'generator-based' coroutine: the one created with types.coroutine
# decorator
@types.coroutine
def receiver():
    print("--- start receiver")
    # suspend execution / yield control / communicate with caller
    r = yield "value request"
    print("--- receiver received {}".format(r))
    return r

def main():
    # We want to call 'outer_af' async function (aka native coroutine)
    # 'await' keyword can't be used here!
    # It can only be used inside another async function.
    print("*** test started")
    c = outer_af(42)  # just prepare coroutine object. It's not running yet.
    print("*** c is {}".format(c))

    # To start coroutine execution call 'send' method.
    w = c.send(None)  # The first call must have argument None

    # Execution of coroutine is now suspended. Execution point is on
    # the 'yield' statement inside the 'receiver' coroutine.
    # It is waiting for another 'send' method to continue.
    # The yielded value can give us a hint about what exectly coroutine
    # expects to receive from us.
    print("*** w = {}".format(w))

    # After next 'send' the coroutines execution would finish.
    # Even though the native coroutine object is not iterable it will
    # throw StopIteration exception on exit!
    try:
        w = c.send(25)
        # w here would not get any value. This is unreachable.
    except StopIteration as e:
        print("*** outer_af finished. It returned: {}".format(e.value))


if __name__ == '__main__':
    main()

Результат выглядит так:

*** test started
*** c is <coroutine object outer_af at 0x7f4879188620>
- start outer_af(42)
-- start inner_af(42)
--- start receiver
*** w = value request
--- receiver received 25
-- received val 25
- inner_af result: inner_af_result
*** outer_af finished. It returned: outer_af_result

Дополнительный комментарий. Похоже, что невозможно передать управление из собственной сопрограммы. yield не допускается внутри async функций! Поэтому необходимо import types и использовать coroutine декоратор. Он творит черную магию! Честно говоря, я не понимаю, почему yield запрещен, так что требуется смесь родных и основанных на генераторе сопрограмм.

person lesnik    schedule 13.12.2017

Нет, это невозможно. Вам нужен цикл событий. Посмотрите, что произойдет, если вы просто вызовете foo():

>>> f = foo()
>>> print(f)
<coroutine object foo at 0x7f6e13edac50>

Итак, вы получили объект сопрограммы, сейчас ничего не выполняется! Только передав его в цикл событий, он запускается. Вы можете использовать asyncio или другой цикл событий, например Curio.

person Community    schedule 24.02.2016