什么机制使Python lambdas无需await关键字即可工作?

时间:2019-07-01 09:25:03

标签: python lambda async-await python-asyncio

我刚刚发现了一些令人惊讶的事情。考虑以下示例:

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,代码也能正常工作。

我了解我们不能在Python await表达式中使用lambda,因此这似乎是一项改善人体工程学的功能,但它给我带来了一些问题:

  1. 这是如何工作的?这个简单的操作是否可以在任何协程函数之前“注入” await
  2. 这是在某处(PEP)记录的吗?
  3. 这还有其他含义吗?我们能否安全地从lambda表达式中调用协程函数并依靠Python来为我们等待事情?

1 个答案:

答案 0 :(得分:10)

任何异步函数都返回awaitable。您不需要立即进行“ await函数调用”,而最终只需要await返回所返回的等待值。也就是说,这两个是等效的:

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

这样,应该很容易看到lambda的调用fn(1)(隐式地)返回了一个awaitable,而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)