Python: как использовать async / await без asyncio? [Дубликат]

Многие многочисленные дубликаты этого вопроса задают вопрос о влиянии округления с плавающей запятой на конкретные числа. На практике легче понять, как это работает, глядя на точные результаты вычислений, а не просто на чтение. Некоторые языки предоставляют способы сделать это - например, преобразование float или double в BigDecimal в Java.

Так как это вопрос, связанный с языком, ему нужны языковые агностические инструменты, такие как как Десятичный преобразование с плавающей запятой .

Применяя его к числам в вопросе, рассматриваемым как удваивает:

0,1 преобразуется в 0,1000000000000000055511151231257827021181583404541015625,

0,2 преобразуется в 0.200000000000000011102230246251565404236316680908203125,

0,3 конвертируется в 0,29999999999999999989897769753748434595763683319091796875 и

0,30000000000000004 преобразуется в 0,3000000000000000444089209850062616169452667236328125.

Добавление первых двух чисел вручную или в десятичный калькулятор, такой как Full Precision Calculator , показывает точную сумму фактических входов: 0.3000000000000000166533453693773481063544750213623046875.

Если округлить до эквивалента 0,3, ошибка округления будет 0.0000000000000000277555756156289135105907917022705078125. Округление до эквивалента 0,30000000000000004 также дает ошибку округления 0,0000000000000000277555756156289135105907917022705078125.

Возвращаясь к конвертеру с плавающей запятой, необработанный шестнадцатеричный показатель для 0.30000000000000004 равен 3fd3333333333334, который заканчивается четной цифрой и, следовательно, является правильным результатом.

5
задан J-qak 25 February 2016 в 22:36
поделиться

4 ответа

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

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

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

Конечно, если ваша функция (сопрограмма) имела внутри yield выражения, она приостанавливала выполнение на каждом yieldcoro.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.

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

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

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

14
ответ дан Veky 25 August 2018 в 04:48
поделиться

Корутинги должны иметь возможность

  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 запрещен, поэтому требуется сборка сопрограмм на основе родных и генераторов.

0
ответ дан lesnik 25 August 2018 в 04:48
поделиться

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

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

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

1
ответ дан user 25 August 2018 в 04:48
поделиться

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

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

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

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

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

1
ответ дан Zah 25 August 2018 в 04:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: