Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Awaitable" is insufficiently expressive, as one may await something other than Futures #542

Open
glyph opened this issue Mar 8, 2018 · 5 comments
Labels
topic: feature Discussions about new features for Python's type annotations

Comments

@glyph
Copy link

glyph commented Mar 8, 2018

In the wild, today, if you await something, there are several potentially valid things to bubble back out from the innermost await

  • an asyncio.Future
  • a curio "trap" (which I gather is defined as something like Tuple[curio.traps.Traps, Any, ...])
  • a Trio trap, (which I gather is defined as something like Iterable[WaitTaskRescheduled] although I haven't quite found the bottom of that stack)
  • a Deferred

Not all of these are mutually compatible. However, Awaitable is parameterized only on one type - the type of its result. This results in code like this:

def some_deferred() -> Awaitable[int]: ...
def some_future() -> Awaitable[int]: ...

async def x() -> Awaitable[List[int]]:
    y = []
    y.append(await some_deferred())
    y.append(await some_future())
    return y

type-checking even though it should fail.

I'd like to be able to express this as

def some_deferred() -> Awaitable[int, Deferred]: ...
def some_future() -> Awaitable[int]: ... # presumably Future is the default

async def x() -> Awaitable[List[int]]:
    y = []
    y.append(await some_deferred()) # <-- automatically specialize to `Awaitable[List[int], Deferred]` here, if possible?
    y.append(await some_future()) # <-- type error!
    return y

This is made slightly more complex by the fact that some awaitables are mutually intelligible - for example, anything that awaits a Deferred should eventually be able to await a Future directly, once we've made a few more changes to Twisted. We could also do this in the other direction if __await__ were changed to pass the loop along. Whereas, some of these types are incompatible by design; my understanding is that await implies a completely different, and somewhat higher-level, scheduling protocol in Trio, for example.

@ilevkivskyi
Copy link
Member

This looks like something quite tricky. After thinking for some time I didn't find a solution that wouldn't require a lot of special casing in type checkers.

(Also note that by agreement async defs are annotated without wrapping return type in Awaitable, see PEP 484.)

@JelleZijlstra
Copy link
Member

I'm actually not sure I understand the example. As Ivan says, the type annotation on x seems wrong—it should be -> List[int].

But I also don't see why you want a TypeError in your second code block. If you run await some_deferred() or await some_future(), either way you will get an int, so you are still simply making a list of ints. some_deferred() itself may return a Deferred[int], but that can just be implemented by making Deferred a subclass of Awaitable.

@gvanrossum
Copy link
Member

gvanrossum commented Mar 12, 2018 via email

@glyph
Copy link
Author

glyph commented Mar 15, 2018

@JelleZijlstra I think you do have a good point, that Deferred should be a subclass of Awaitable and appropriately generic.

One really nasty thing about the way Deferreds are constructed, unfortunately is that the stubs want to look something like this:

class Deferred(Generic[T]):
    def addCallback(self, cb: Callable[[T], T2], ...) -> Deferred[T2]: ...

Except, uh, addCallback returns self and its type changes at runtime.

I think maybe just pretending that it doesn't is the best way forward for type annotations in Twisted but I do wish there were some way to express this.

@glyph
Copy link
Author

glyph commented Mar 15, 2018

It seems Glyph would like mypy to tell him that he's (incorrectly, presumably) mixing await <future> and await <deferred> in the same code.

Yep. Fixing this is pretty easy even now: await deferred.asFuture(loop) or await Deferred.fromFuture(future) depending on which way you're going. But you do still need to be explicit about it.

@srittau srittau added the topic: feature Discussions about new features for Python's type annotations label Nov 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: feature Discussions about new features for Python's type annotations
Projects
None yet
Development

No branches or pull requests

5 participants