Step 7 - The Future¶
Looking at our current processing chain loop -> yield from* -> yield (the *
means it can be repeated n times) the yield part on the right side is a bit
out-of-order. It just doesn’t feel right completely. Wouldn’t it be better if
get a well defined API that abstracts the yield and yield from can be used
instead.
Let us try something. How does a coroutine work normally? First it runs and starts something. Afterwards it suspends itself and gives back control to the caller. If something is done and it is resumed, a result is returned.
from typing import Any, Generator
class Future:
    """Return a result in the future v1"""
    _result = None
    def set_result(self, result: Any):
        self._result = result
    def __iter__(self) -> Generator[None, None, Any]:
        yield
        return self._result
We introduce a new class Future that can be called with yield from future
to first suspend and give the control back to the caller and second to return a
result. Additionally a Future can be in one of the following states:
Pending - Waiting for a result
Done - A result is set
It always takes (at least) two generator steps to get the into the done state.
Note
As a side note, the Future class implements the Iterable protocol.
Example:
def future_result(future):
    # something might happen here too
    return (yield from future)
future = Future()
coroutine = future_result(future)
next(coroutine)
# the result of the future is set somewhere
# we just fake setting it here
future.set_result(123)
try:
    next(coroutine)
except StopIteration as e:
    result = e.value
    print(result)
Hint
To clarify how the Future works, future_result could be rewritten as
def future_result(future):
    # __iter__ implements the Iterable protocol and
    # returns a coroutine/generator/iterator
    coroutine = future.__iter__()
    return (yield from coroutine)
or even more detailed without yield from
def future_result(future):
    # __iter__ implements the Iterable protocol and
    # returns a coroutine/generator/iterator
    coroutine = future.__iter__()
    try:
        while True:
            x = next(coroutine)
            yield x
    except StopIteration as e:
        return e.value
As a result we have an object that allows to set a result (even) in a future step.
Example Usage:
def do_something(future):
    # do something, for example:
    # * start a thread
    # * wait for something being returned from the thread via a callback
    def on_result_from_thread(result):
        future.set_result(result)
    thread = thread.create()
    thread.on_exit = on_result_from_thread
def some_result():
    future = Future()
    do_something(future)
    return (yield from future)
Simple example without real purpose:
from future import Future
from loop import loop
def some_result(value):
    # just a coroutine to return some value
    future = Future()
    # do something, we just fake it here
    future.set_result(value)
    return (yield from future)
def add(coroutine1, coroutine2):
    x = yield from coroutine1
    y = yield from coroutine2
    return x + y
def main():
    return (yield from add(some_result(1), some_result(2)))
result = loop(main())
print("Loop finished with result", result)
Output:
Loop step 1
Loop step 2
Loop step 3
Loop finished with result 3
Summary
We learned the basic concept of a
Future