[Python-ideas] An async facade? (was Re: [Python-Dev] Socket timeout and completion based sockets)

Guido van Rossum guido at python.org
Fri Nov 30 20:29:15 CET 2012

On Fri, Nov 30, 2012 at 11:18 AM, Steve Dower <Steve.Dower at microsoft.com> wrote:
> Guido van Rossum wrote:
>> Futures or callbacks, that's the question...
> I know the C++ standards committee is looking at the same thing right now, and they're probably going to provide both: futures for those who prefer them (which is basically how the code looks) and callbacks for when every cycle is critical or if the developer prefers them. C++ has the advantage that futures can often be optimized out, so implementing a Future-based wrapper around a callback-based function is very cheap, but the two-level API will probably happen.

Well, for Python 3 we will definitely have two layers already:
callbacks and yield-from-based-coroutines. The question is whether
there's room for Futures in between (I like layers of abstraction, but
I don't like having too many layers).

>> Richard and I have even been considering APIs like this:
>> res = obj.some_call(<args>)
>> if isinstance(res, Future):
>>     res = yield res
>> or
>> res = obj.some_call(<args>)
>> if res is None:
>>     res = yield <magic>
>> where <magic> is some call on the scheduler/eventloop/proactor that pulls the future out of a hat.
>> The idea of the first version is simply to avoid the Future when the result happens to be immediately
>> ready (e.g. when calling readline() on some buffering stream, most of the time the next line is
>> already in the buffer); the point of the second version is that "res is None" is way faster than
>> "isinstance(res, Future)" -- however the magic is a little awkward.
>> The debate is still open.
> How about:
> value, future = obj.some_call(...)
> if value is None:
>     value = yield future

Also considered; I don't really like having to allocate a tuple here
(which is impossible to optimize out completely, even though its
allocation may use a fast free list).

> Or:
> future = obj.some_call(...)
> if future.done():
>     value = future.result()
> else:
>     value = yield future

That seems the most expensive option of all because of the call to
done() that's always there.

> I like the second one because it doesn't require the methods to do anything special to support always yielding vs. only yielding futures that aren't ready - the caller gets to decide how performant they want to be. (I would also like to see Future['s base class] be implemented in C and possibly even preallocated to reduce overhead. 'done()' could also be an attribute rather than a method, though that would break the existing Future class.)

Note that in all cases the places where this idiom is *used* should be
few and far between -- it should only be needed in the "glue" between
the callback-based world and the coroutine-based world. You'd only be
writing new calls like this if you're writing new glue, which should
only be necessary if you are writing wrappers for (probably
platform-specific) new primitive operations supported by the lowest
level event loop.

This is why I am looking for the pattern that executes fastest rather
than the pattern that is easiest to write for end users -- the latter
would be to always return a Future and let the user write

  res = yield obj.some_call(<args>)

--Guido van Rossum (python.org/~guido)

More information about the Python-ideas mailing list