Какой механизм заставляет лямбды Python работать без ключевого слова await? - PullRequest
9 голосов
/ 01 июля 2019

Я только что заметил что-то удивительное.Рассмотрим следующий пример:

import asyncio

async def wait_n(n):
    asyncio.sleep(n)

async def main(fn):
    print("meh")
    await fn(1)
    print("foo")

loop = asyncio.get_event_loop()
loop.run_until_complete(main(wait_n))

Когда мы запустим это, мы по праву получим следующее предупреждение:

awaitable_lambda.py:5: RuntimeWarning: coroutine 'sleep' was never awaited

asyncio.sleep (n)

Это потому, чтов wait_n мы назвали asyncio.sleep(n) без await.

Но теперь рассмотрим второй пример:

import asyncio

async def main(fn):
    print("meh")
    await fn(1)
    print("foo")

loop = asyncio.get_event_loop()
loop.run_until_complete(main(lambda n: asyncio.sleep(n)))

На этот раз мы используем lambda и на удивление код работаетпросто отлично, хотя нет await.

Я понимаю, что мы не можем использовать await внутри выражения Python lambda, так что это похоже на функцию улучшения эргономики, ноэто вызывает у меня несколько вопросов:

  1. Как именно это работает?Означает ли это простое «введение» await перед любой функцией сопрограммы?
  2. Это где-то задокументировано (PEP)?
  3. Есть ли другие последствия этого?Можем ли мы просто безопасно вызывать функции сопрограммы из лямбда-выражений и полагаться на Python, который нас ждет?

1 Ответ

10 голосов
/ 01 июля 2019

Любая асинхронная функция возвращает ожидаемое .Вам не нужно "await вызов функции" немедленно, вам просто нужно await возвращенное ожидаемое значение в конце концов.То есть эти два эквивалента:

await asyncio.sleep(1)
awaitable = asyncio.sleep(1)
await awaitable

Таким образом, должно быть легко увидеть, что вызов fn(1) лямбды (неявно) возвращает ожидаемое, а await ждет его,async def wait_n, с другой стороны, никогда не возвращает ожидаемое sleep и никогда не ожидает его само.

В качестве следствия этого, если у вас есть обертка вокруг функции async, не обязательнонужно, чтобы эта оболочка была async сама по себе:

def add_1(func):
    def wrapper(a):
        return func(a + 1)  # passes the awaitable return value through

    return wrapper

@add_1
async def foo(a):
    await asyncio.sleep(a)

async def main():
    await foo(1)
...