[Async-sig] Optional async method and best practices

Chris Jerdonek chris.jerdonek at gmail.com
Wed Jul 12 16:27:15 EDT 2017


On Wed, Jul 12, 2017 at 9:44 AM, Laurent Mazuel via Async-sig
<async-sig at python.org> wrote:
>     @property
>     def future(self):
>         if self._future is None:
>             self._future = asyncio.Future()
>             asyncio.ensure_future(self._poll())
>         return self._future
>
> result = await poller.future
>
> If I don't call the "future" attribute, I don't poll at all. My initial "create" method returns the same object anytime, and I don't need a parameter or another "nowait" method. Do you have any caveat for issues I don’t see in this approach?

Hi Laurent, it seems like there is still an issue with this approach
in that merely accessing / inspecting the property has the side effect
of creating a coroutine object (calling self._poll()), and so can
trigger the warning in innocent-looking code:

    loop = asyncio.get_event_loop()
    poller = PollerOperation()
    fut = poller.future  # creates coroutine object
    if False:
        loop.run_until_complete(fut)
    loop.close()

I'm not sure if others feel differently, but property access IMO
shouldn't have possible side effects like this. If there are possible
negative side effects, it should be a method call to indicate to the
user that it is "doing" something that warrants more consideration.

--Chris

>
> Thank you very much!!!
>
> Laurent
>
> PS: Cory, I just subscribed to the mailing list 😊
>
> -----Original Message-----
> From: Cory Benfield [mailto:cory at lukasa.co.uk]
> Sent: Wednesday, July 12, 2017 01:18
> To: Laurent Mazuel <lmazuel at microsoft.com>
> Cc: async-sig at python.org
> Subject: Re: [Async-sig] Optional async method and best practices
>
>
>> On 11 Jul 2017, at 23:26, Laurent Mazuel via Async-sig <async-sig at python.org> wrote:
>>
>> Hello,
>
> Hi Laurent! A future note: your message got stuck in moderation because you aren’t subscribed to the mailing list. You may find it helpful to subscribe, as your future messages will also get stuck unless you do!
>
>> My first prototype was to split and return a tuple (resource, coroutine):
>>
>>    obj, optional_poller = client.create(**parameters)
>>    obj = await optional_poller # OPTIONAL
>>
>> But I got a warning if I decide to do not use this poller, RuntimeWarning: coroutine 'foo' was never awaited
>>
>> I was surprised honestly that I can't do that, since I feel like I'm not leaking anything. I didn't run the operation, so there is no wasted resource at my knowledge. But I remember wasting time because of a forgotten "yield from", so I guess it's fair 😊. But I would be curious to understand what I did badly.
>
> The assumption in asyncio, generally speaking, is that you do not create coroutines you do not care about running. This is both for abstract theoretical reasons (if you don’t care if the coroutine is run or not, why not just optimise your code to never create the coroutine and save yourself the CPU cycles?) and for more concrete practical concerns (coroutines may own system resources and do cleanup in `finally` blocks, and if you don’t await a coroutine then you’ll never reach the `finally` block and so will leak system resources).
>
> Given that there’s no computational way to be sure that *not* running a coroutine is safe (hello there halting problem), asyncio takes the pedantic model and says that not running a coroutine is a condition that justifies a warning. I think asyncio’s position here is correct, incidentally.
>
>> 2- Return my initial object with an async method. This allows me to write (something finally close to the current code):
>>
>>    async_poller = client.create(**parameters)
>>    obj = async_poller.resource() # Get the initial resource information, but the object is not actually created yet.
>>    obj = await async_poller.result() # OPTIONAL
>>
>> My async_poller object being something like:
>>
>>    class PollerOperation:
>>         async def result(self):
>>              ...async version of previous sync result()...
>>
>> So the questions are:
>> - Does this seem a correct pattern?
>
> Yes. This is the simple map to your old API, and is absolutely what I’d recommend doing in the first instance if you want to use coroutines.
>
>
>> - Is there a simple way to achieve something like this:
>>
>>    obj = await async_poller
>>
>> meaning, I can win the "result()" syntax and directly "await" on the object and get the result from magic function. I tried by subclassing some ABC coroutine/awaitable, but wasn't able to find a correct syntax. I'm not even sure this makes sense and respects the zen of Python 😊
>
> There are a few other patterns you could use.
>
> The first is to return a Future, and just always run the “polling” function in the background to resolve that future. If the caller doesn’t care about the result they can just ignore the Future, and if they do care they can await on it. This has the downside of always requiring the I/O to poll, but is otherwise pretty clean.
>
> Another option is to offer two functions, for example `def resource_nowait()` and `async def resource`. The caller can decide which they want to call based on whether they care about finding out the result. This is the clearest approach that doesn’t trigger automatic extra work like the Future does, and it lacks magic: it’s very declarative. This is a nice approach for keeping things clean.
>
> Finally, you can create a magic awaitable object. PEP 492 defines several ways to create an “awaitable”, but one approach is to use what it calls a “Future-like object”: that is, one with a __await__ method that returns an iterator. In this case, you’d do a very basic extension of the Future object by triggering the work upon the call of __await__ before delegating to the normal behaviour. This is an annoyingly precise thing to do, though technically do-able.
>
> Cory
> _______________________________________________
> Async-sig mailing list
> Async-sig at python.org
> https://mail.python.org/mailman/listinfo/async-sig
> Code of Conduct: https://www.python.org/psf/codeofconduct/


More information about the Async-sig mailing list