typing icon indicating copy to clipboard operation
typing copied to clipboard

Shorthand for async function

Open retnikt opened this issue 6 years ago • 8 comments

Following on from #424, it would be nice to have something like AsyncFunction[[Args], ReturnType] instead of Callable[[Args], Coroutine[ReturnType]].

retnikt avatar Aug 02 '19 12:08 retnikt

TBH, I don't think this is a big win in terms of readability or number of keystrokes. I think however we can keep this open to see if anyone else is interested in such "shorthand", since implementing this is pretty easy.

ilevkivskyi avatar Aug 07 '19 11:08 ilevkivskyi

If this does ever get approved, it should be noted that GeneratorFunction could be useful too.

retnikt avatar Sep 14 '19 06:09 retnikt

I would like to provide a simple sample that literally cries for a shorthand.

This is a sample from aiohttp.web docs https://docs.aiohttp.org/en/stable/web_advanced.html#middleware-factory which when annotated looks like this:

import typing

from aiohttp import web


def middleware_factory(
    text: str
) -> typing.Callable[
    [web.Request, typing.Callable[[web.Request], typing.Awaitable[web.Response]]],
    typing.Awaitable[web.Response],
]:
    @web.middleware
    async def sample_middleware(
        request: web.Request,
        handler: typing.Callable[[web.Request], typing.Awaitable[web.Response]],
    ) -> web.Response:
        resp = await handler(request)
        resp.text = (resp.text if resp.text is not None else '') + text
        return resp

    return sample_middleware

The shorthand would probably remove only typing.Awaitable things but it looks like the code will look quite a bit more understandable.

lig avatar Nov 26 '19 14:11 lig

I think an additional readability benefit beyond character count is that the async vs not-async distinction is front-loaded in the spec. Currently you must look at the end of the Callable declaration to know whether it's an async function. Consider the following type specs:

F1 = Callable[[Dict[str, Any], str, Any], Iterable[str]]
F2 = Callable[[Dict[str, Any], str, Any], Awaitable[str]]

When I read this, by the time I get through the parameter spec my brain is telling me that F1 and F2 are really similar and can be called the same way, even though I have to read one step further to learn that the return value indicates that the latter must be awaited. IMO we should front load this information so that the difference is much more obvious:

F1 = Callable[[Dict[str, Any], str, Any], Iterable[str]]
F2 = AsyncCallable[[Dict[str, Any], str, Any], str]

rmorshea avatar Jan 13 '20 20:01 rmorshea

To get a better idea of how big the impact might be, it would be interesting to see some statistics about how frequently this could be used in some non-trivial real-world async codebase. For example, if there are two instances in a 5000-line codebase where this would help, the benefit would be pretty marginal, but if there are 50 instances, this could be a big potential win.

JukkaL avatar Jan 14 '20 11:01 JukkaL

Example Codebase: Starlette

$ find starlette/ -name '*.py' | xargs wc -l
[...]
 10151 total
$ grep -re 'Callable\[.*Awaitable.*\]' starlette/ | wc -l
6

6 instances in a 10k line codebase, so perhaps not a massive priority, but I see no reason not to add it?

retnikt avatar Jan 21 '20 20:01 retnikt

Example Codebase: Faust

$ find faust/ -name '*.py' | xargs wc -l
[...]
  62709 total
$ grep -re 'Callable\[.*Awaitable.*\]' faust/ | wc -l
26

26 instances in a 62k line codebase, so like @retnikt said, perhaps not a massive priority.

That said, @rmorshea's argument really speaks to me. Even if AsyncCallable[[Args], ReturnType] is just a shorthand for Callable[[Args], Awaitable[ReturnType]] I think there are some major readability benefits. I'd personally prefer to write the former if possible.

Unless I'm mistaken there may already be some prior art in the form of names like AsyncIterable, AsyncContextManager, AsyncGenerator and friends.

reillysiemens avatar Feb 26 '20 08:02 reillysiemens

The question is how often there is # type: ignore in such a place as its almost impossible to guess this correctly or write directly from one's memory.

lig avatar Mar 28 '20 16:03 lig