The right way to type hint a Coroutine function?

Question:

I cannot wrap my head around type hinting a Coroutine. As far as I understand, when we declare a function like so:

async def some_function(arg1: int, arg2: str) -> list:
    ...

we effectively declare a function, which returns a coroutine, which, when awaited, returns a list. So, the way to type hint it would be:

f: Callable[[int, str], Coroutine[???]] = some_function

But Coroutine generic type has 3 arguments! We can see it if we go to the typing.py file:

...
Coroutine = _alias(collections.abc.Coroutine, 3)
...

There is also Awaitable type, which logically should be a parent of Coroutine with only one generic parameter (the return type, I suppose):

...
Awaitable = _alias(collections.abc.Awaitable, 1)
...

So maybe it would be more or less correct to type hint the function this way:

f: Callable[[int, str], Awaitable[list]] = some_function

Or is it?

So, basically, the questions are:

  1. Can one use Awaitable instead of Coroutine in the case of type hinting an async def function?
  2. What are the correct parameters for the Coroutine generic type and what are its use-cases?
Asked By: winwin

||

Answers:

As the docs state:

Coroutine objects and instances of the Coroutine ABC are all instances of the Awaitable ABC.

And for the Coroutine type:

A generic version of collections.abc.Coroutine. The variance and order of type variables correspond to those of Generator.

Generator in turn has the signature Generator[YieldType, SendType, ReturnType]. So if you want to preserve that type information, use Coroutine, otherwise Awaitable should suffice.

Answered By: isaactfa

The Coroutine type takes the same signature as the Generator type:

Generator[YieldType, SendType, ReturnType]

Since the result of an unawaited call to a coroutine is an awaitable (it can be used in an await expression), it can be type hinted with:

Awaitable[ReturnType]

Example:

async def some_function(arg1: int, arg2: str) -> List[str]:
    return ['foo']

coro: Awaitable[List[str]] = some_function(1, 'bar')
result = await coro
print(result)
# prints ['foo']

However, for type hinting a coroutine, I do not find either of these useful on their own.
Instead, I opt for something similar to what you stated in your last example:

def return_coro() -> Callable[[int, str], Awaitable[List[str]]]:
    async def some_function(arg1: int, arg2: str) -> List[str]:
        return ['foo']
    return some_function

Note mypy will get upset if you try to pass this into a function which explicitly expects a Coroutine (such as asyncio.run()).

Answered By: Luke Purnell