29

I know in js it doesn't add anything to do an await before a return statement (i.e. return await ...), but is it the case in python too, or this somehow makes the materialization more probable or different?

If the two are not equivalent, what is the best practice?

Uri
  • 25,622
  • 10
  • 45
  • 72
  • Does this answer your question? [When to use and when not to use Python 3.5 \`await\` ?](https://stackoverflow.com/questions/33357233/when-to-use-and-when-not-to-use-python-3-5-await) – shaik moeed Dec 10 '19 at 04:36
  • @shaikmoeed this question is much more specific and deserves its own answer (in js there's a linter to prevent this pattern, which illustrates people are thinking about this specific thing). – Uri Dec 10 '19 at 13:07
  • 2
    @Uri "I know in js it doesn't add anything to do an await before a return statement" This is not true, even though quite close. Read this: https://jakearchibald.com/2017/await-vs-return-vs-return-await/ Thus the rule of thumb should be: always `await`. I'm not sure how Python handles it, but Js is just a shitty language that does lots of implicit, counterintuitive and hard-to-debug things behind the scene. – freakish Dec 10 '19 at 13:25

2 Answers2

39

Given:

async def foo() -> str:
    return 'bar'

What you get when calling foo is an Awaitable, which obviously you'd want to await. What you need to think about is the return value of your function. You can for example do this:

def bar() -> Awaitable[str]:
    return foo()  # foo as defined above

There, bar is a synchronous function but returns an Awaitable which results in a str.

async def bar() -> str:
    return await foo()

Above, bar itself is async and results in an Awaitable when called which results in a str, same as above. There's no real difference between these two usages. Differences appear here:

async def bar() -> Awaitable[str]:
    return foo()

In that example, calling bar results in an Awaitable which results in an Awaitable which results in a str; quite different. If you naïvely use the above, you'll get this kind of result:

>>> asyncio.run(bar())
<coroutine object foo at 0x108706290>
RuntimeWarning: coroutine 'foo' was never awaited

As a rule of thumb, every call to an async must be awaited somewhere once. If you have two async (async def foo and async def bar) but no await in bar, then the caller of bar must await twice, which would be odd.

TankorSmash
  • 12,186
  • 6
  • 68
  • 106
deceze
  • 510,633
  • 85
  • 743
  • 889
  • Thanks! Would it have made more sense for `asyncio` to merge the coroutines, requiring a single await? Seems like an odd design choice. – Uri Dec 10 '19 at 15:41
  • 5
    Python's mantra is *explicit is better than implicit*, so one `await` per `async` makes a whole lot of sense and leaves you in total control. – deceze Dec 10 '19 at 15:44
16

TL)DR of @deceze answer.

Yes, there is a reason. Always return await from a coroutine when calling another coroutine.

Async functions always return an Awaitable, even with a plain return. You only get the actual result by calling await. Without return await the result is an extra wrapped Awaitable and must be awaited twice. See doc.

import asyncio

async def nested():
    return 42

async def main():
    # Nothing happens if we just call "nested()".
    # A coroutine object is created but not awaited,
    # so it *won't run at all*.
    nested()

    # Let's do it differently now and await it:
    print(await nested())  # will print "42".

asyncio.run(main())

dre-hh
  • 7,840
  • 2
  • 33
  • 44