[Python-ideas] Allow async defs for __await__

Thomas Gläßle t_glaessle at gmx.de
Thu Feb 28 15:23:13 EST 2019


Hi,

I suggest to allow defining awaitable classes in terms of `async def`-s:

    class FeynmanAlgorithm:
        async def __await__(self):
            await self.write_down_the_problem()
            await self.think_real_hard()
            await self.write_down_the_solution()

IMO, this would be the "one obvious way" for anyone who's using it for
anything other than defining low-level primitives. It is also easier to
teach and and more consistent with how you can define iterables - where
`__iter__` is explicitly allowed to be defined in terms of generator
function (which I would consider the equivalent to the async defs of
awaitables).

The implementation could be as easy as calling `__await__` as long as
the returned object is not an iterator instead of going straight to
TypeError.



Currently `__await__` must return an iterator, and one has to define a
separate function or method (or use a decorator) to do the same, e.g.:

    class FeynmanAlgorithm:
        def __await__(self):
            return self.perform().__await__()
        async def perform(self):
            [...]

IMO, this is far from obvious and has been subject to a few subtle
differences between python versions. For example:

    import asyncio

    class Waiter:
        def __await__(self):
            return asyncio.sleep(1).__await__()

    asyncio.get_event_loop().run_until_complete(
        asyncio.ensure_future(Waiter()))

works fine on python 3.7 but fails on python 3.5 with "AttributeError:
'generator' object has no attribute '__await__'" because some awaitables
are implemented using generator type coroutines.


Adding to potential confusion, `async def __await__` seems to be work in
certain situations:

    import asyncio

    class Waiter:
        async def __await__(self):
            await asyncio.sleep(1)

    # this works on py3.7:
    asyncio.get_event_loop().run_until_complete(
        asyncio.ensure_future(Waiter()))

but, awaiting them in a coroutine context causes an error:

    async def foo():
        await Waiter()

    # "TypeError: __await__() returned a coroutine":
    asyncio.get_event_loop().run_until_complete(
        asyncio.ensure_future(foo()))


In earlier python versions (at least up to 3.5) the situation is
somewhat reversed. The `yield from` analogue of the above used to work
in coroutine context:

    import asyncio

    class Waiter:
        def __iter__(self):
            yield from asyncio.sleep(1)

    @asyncio.coroutine
    def foo():
        yield from Waiter()

    # works on py3.5:
    asyncio.get_event_loop().run_until_complete(
        asyncio.ensure_future(foo()))

While the following doesn't:

    # "TypeError: An asyncio.Future, a coroutine or an awaitable is
required":
    asyncio.get_event_loop().run_until_complete(
        asyncio.ensure_future(Waiter()))

What do you think?

Kind regards,
Thomas



See also:

https://stackoverflow.com/questions/33409888/how-can-i-await-inside-future-like-objects-await
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 963 bytes
Desc: OpenPGP digital signature
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20190228/08bf56d4/attachment.sig>


More information about the Python-ideas mailing list