2016-02-23 7 views
3

Is можно запустить функцию как этотЗапустить функцию асинхронной без импорта пакета asyncio

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

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

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

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

+1

Что касается истинных сопрограмм на Python 3.5, я сомневаюсь в этом, потому что что-то должно вращать колесо, так сказать. Хотя вы могли бы попробовать внедрить сопрограмму «старомодным» способом, создав генератор python и подав ее с помощью команд 'send()' – shongololo

+0

. Почему вы не хотите импортировать 'asyncio'? – dirn

ответ

9

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

В этом случае, поскольку ваша функция не имеет await точек внутри, для этого требуется всего лишь одно нажатие. Вы нажимаете coroutine на send ing 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 s начинают получать утомительно. Было бы неплохо получить их полуавтоматически.

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 РЕПЛ печатали подсказки в окно консоли, и рассчитывает на вас, чтобы обеспечить события, набрав t.send(whatever) в него :)

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

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

+1

Замечательно, я не могу дождаться, чтобы проверить его! –

+0

Если у вас есть еще вопросы, просто спросите. Я буду рад ответить. – Veky

1

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

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

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

1

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

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

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

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

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

0

Сопрограмма должна быть в состоянии

  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 

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

Смежные вопросы