49

I am trying to make sure a function parameter is an async function. So I am playing around with the following code:

async def test(*args, **kwargs):
    pass

def consumer(function_: Optional[Coroutine[Any, Any, Any]]=None):
    func = function_

consumer(test)

But it doesn't work.

I am presented with the following error during type checking in PyCharm:

Expected type 'Optional[Coroutine]', got '(args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Coroutine[Any, Any, None]' instead

Can anyone give me some hints how to solve this?

1
  • What happens if you pass test() into consumer? According to python 3.7 type(test) is function, while type(test()) is coroutine, despite test being a coroutine and pyre-check finding no errors in your code. Commented Oct 23, 2018 at 17:55

2 Answers 2

59

You are looking for:

FuncType = Callable[[Any, Any], Awaitable[Any]]
def consumer(function_: FuncType = None):
    pass  # TODO: do stuff

Why is the type structured like that? If you declare a function async, what you actually do is wrap it in a new function with the given parameters, which returns a Coroutine (which is an Awaitable).


Since this might be relevant to some people who come here, this is an example of an awaitable function type with in and out typed:

OnAction = Callable[[Foo, Bar], Awaitable[FooBar]]

It is a function that takes Foo, Bar and returns a FooBar

5
  • when I try out the FuncType you propose, mypy seems to be equally happy with normal functions as async functions. so this type does not seem to constrain you to only async def style functions.
    – hwjp
    Commented Jun 18, 2020 at 8:40
  • ah ok i think i see what's going on. this works only if your callbacks have good type hints themselves. specifically, if they define their return types. otherwise even a non-async function has return type Any, which mypy will say type checks against Coroutine[Any]. leastways i think that's what's going on.
    – hwjp
    Commented Jun 18, 2020 at 9:13
  • @hwjp yea, of course. Coroutine is just the return type of an async function. So if your function has no return type declared, it could secretly behave like an async one.
    – Neuron
    Commented Jun 24, 2020 at 15:03
  • 4
    Type hints should use Awaitable instead of Coroutine - the relevant thing is almost always "can I use await on this?", not implementation details like whether you're looking at an awaitable defined with async def as opposed to some object with an __await__ method or whatever.
    – mtraceur
    Commented Jun 20, 2022 at 22:22
  • @mtraceur Not true. the other relavent thing is "do I need to await this thing". Linters will warn you if you simple call (not await) an async function. They will not warn you if you create a generic awaitable and do not await it. Commented 2 days ago
15

I can't help you too much, especially because right now (PyCharm 2018.2) this error is not raised in Pycharm anymore.

At present, type hints are somewhere between reliable metadata for reflection/introspection and glorified comments which accept anything the user puts in. For normal data structures this is great (my colleague even made a validation framework based on typing), but things get more complicated when callbacks and async functions come into play.

Take a look at these issues:

https://github.com/python/typing/issues/424 (open as of today) - async typing https://github.com/python/mypy/issues/3028 (open as of today) - var-args callable typing

I would go with:

from typing import Optional, Coroutine, Any, Callable


async def test(*args, **kwargs):
    return args, kwargs


def consumer(function_: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None):
    func = function_
    return func


consumer(test)

I don't guarantee they meant exactly that, but my hint is built like this:

Optional - sure, can be None or something, in this case:

Callable - something which can be invoked with (), ... stands for any argument, and it produces:

Coroutine[Any, Any, Any] - this is copied from OP, and very general. You suggest that this function_ may be await-ed, but also receive stuff send()-ed by consumer, and be next()-ed / iterated by it. It may well be the case, but...

If it's just await-ed, then the last part could be:

Awaitable[Any], if you actually await for something or

Awaitable[None], if the callback doesn't return anything and you only expect to await it.

Note: your consumer isn't async. It will not really await your function_, but either yield from it, or do some loop.run_until_complete() or .create_task(), or .ensure_future().

1
  • I was just looking for the definition of Awaitable thanks to your answer, I now know that's in typing. BTW, the issues mentioned were closed in 2021.
    – Wolf
    Commented Jan 18, 2023 at 12:31

Not the answer you're looking for? Browse other questions tagged or ask your own question.