Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use super() in an awaitable subclass?

I'd like to add a new functionality to an existing awaitable class by subclassing it.

Let's start with a very simple base class creating objects which asynchronously return 99 after a short sleep. The subclass should just add +1 to the result.

I can't find the proper way to use super() to refer to the base class.

import asyncio

class R99:
    def __await__(self):
        loop = asyncio.get_event_loop()
        fut = loop.create_future()
        loop.call_later(0.5, fut.set_result, 99)
        return fut.__await__()

class R100(R99):
    async def add1(self):
        v = await R99()
        #v = await super().__await__()   # <== error
        return v + 1

    def __await__(self):
        return self.add1().__await__()

async def test():
    print(await R99())
    print(await R100())

asyncio.get_event_loop().run_until_complete(test())
like image 362
VPfB Avatar asked Oct 02 '18 09:10

VPfB


2 Answers

The await method must return an iterator, so you can make it a generator and use the yield from syntax:

class R100(R99):

    def __await__(self):
        v = yield from super().__await__()
        return v + 1
like image 199
Vincent Avatar answered Nov 17 '22 12:11

Vincent


If you are allowed to modify R99, you can make __await__() invoke an actual coroutine, which can chain to super() in the regular way:

import asyncio

class R99:
    async def await_coro(self):
        loop = asyncio.get_event_loop()
        fut = loop.create_future()
        loop.call_later(0.5, fut.set_result, 99)
        return await fut

    def __await__(self):
        return self.await_coro().__await__()

class R100(R99):
    async def await_coro(self):
        v = await super().await_coro()
        return v + 1

If that is not an option, @Vincent's answer precisely explains how to chain from one __await__ to another. Note that you were quite correct in thinking that await is the new yield from - it is, and there is normally no reason to use yield from in newly written asyncio code. (This of course doesn't apply to non-asyncio-related generators that delegate to sub-generators; those are welcome to keep using yield from.)

However, by implementing __await__() you are dropping into the lower-level API which uses generators to implement coroutines. At this level yield suspends the coroutine, returning control to the event loop, and yield from delegates to another generator that implements a coroutine. In new code the only valid purpose for this layer is to implement an awaitable object without using the interpreter, e.g. in Python/C or Cython. This is done by providing an __await__ that returns an iterator, as shown here. The resulting object is equivalent to an async def.

like image 34
user4815162342 Avatar answered Nov 17 '22 11:11

user4815162342