Yield

https://www.geeksforgeeks.org/use-yield-keyword-instead-return-keyword-python/

Yield will suspend the execution of a function, sending a value back to the caller, but retain enough state to enable the function to resume where it left off.

  • execution resumes immediately where it left off
  • produce series of value over time without computing all at once and returning a list

Use yield when we want to iterate over a sequence without returning all of it at once (and having to store it all in memory). The normal return keyword would just give us everything and the function would be done.

Generators

Generators produce data - they are functions that contain the yield keyword automatically become generators.

The generator function returns a generator object which can be iterated over:

  • call the next() function with the generator as an argument
  • place in a for loop
  • can't accept arguments after initialized

Previously, generators had to be completely consumed by the function calling them and couldn't call another generator which would halt the execution of both. However, there is now the yield from syntax which can be used to yield the result from another generator.


In [1]:
def generator1(): 
    yield 1
    yield 2
    yield 3

for value in generator1():  
    print(value)


1
2
3

In [2]:
def generator2():
    yield "Hello"
    yield "World"

my_gen = generator2()
print(next(my_gen))
print(next(my_gen))


Hello
World

Coroutines

Similar to threads, but a coroutine decides when to switch context based on the code written. Threads would switch based on the operating system (or runtime environment). Coroutines are coopertive and multitask based on how the programmer has set suspend and resume points in the code.

Similar to generators:

  • extra methods
  • tweaked yield statement
  • Coroutines also consume data
  • can accept arguments after it's initialized

With a modified yield statement, data can be sent to a coroutine:


In [3]:
def check_for_value(num):
    try:
        while True:
            received_value = (yield)       #data sent to this co is stored here
            if num == received_value:      #check the value that was sent
                print("Correct!")
            else:
                print("Incorrect")
    except GeneratorExit:                  #catches coroutine.close()
        print("Closing Coroutine.")

In [4]:
coroutine = check_for_value(42)        #instantiating the coroutine w/42

In [5]:
coroutine.__next__()                   #starts co and pauses at 1st yield

In [6]:
coroutine.send(7)


Incorrect

In [7]:
coroutine.send(42)


Correct!

In [8]:
coroutine.close()                      #close coroutine


Closing Coroutine.

Coroutine Pipelines

Coroutines can be chained together to form a pipeline, starting with a producer as a simple function, going into a middle coroutine, and ending with an output coroutine. Data gets passed from one stage to another.


In [9]:
def producer(data_in, next_coroutine):
    tokens = data_in.split(" ")
    for token in tokens:
        next_coroutine.send(token)
    next_coroutine.close()

In [10]:
def middle_coroutine(filter_str, next_coroutine):
    print("Search for words containing: ", filter_str)
    try:
        while True:
            token = (yield)
            if filter_str in token:
                next_coroutine.send(token)
    except GeneratorExit:
        print("Done filtering")
        next_coroutine.close()

In [11]:
def output_coroutine():
    try:
        while True:
            token = (yield)
            print(token)
    except GeneratorExit:
        print("Done printing")

In [12]:
sink = output_coroutine()
sink.__next__()

In [13]:
middle = middle_coroutine("t", sink)
middle.__next__()
input_str = "This is a sentence with some words containing the letter t."
producer(input_str, middle)


Search for words containing:  t
sentence
with
containing
the
letter
t.
Done filtering
Done printing

Asynchronous Python

Asynchronous programming is where execution order is not 100% known ahead of time. Some parts of the code may execute before other parts, with no way to guarantee the order.

Event Loop

Programming construct that waits for events to happen and dispatches responses to them. The asyncio library was added to python to provide an event loop.

Yield From and Wait (python 3.4)

Reference: https://hackernoon.com/asynchronous-python-45df84b82434

Use the decorator "@asyncio.coroutine" on the generator (function) and it will now be a coroutine meant for use with asyncio and its event loop (it can also call another generator)

  • use the keywords yield from instead of just yield
  • line containing yield from blocks until it's completely finished, then moves on

In [14]:
import asyncio

# Borrowed from http://curio.readthedocs.org/en/latest/tutorial.html.
@asyncio.coroutine
def countdown(number, n):
    while n > 0:
        print('T-minus', n, '({})'.format(number))
        yield from asyncio.sleep(1)
        n -= 1

asyncio.set_event_loop(asyncio.new_event_loop())
loop = asyncio.get_event_loop()

tasks = [asyncio.ensure_future(countdown("A", 2)), asyncio.ensure_future(countdown("B", 3))]
loop.run_until_complete(asyncio.wait(tasks))

loop.close()


T-minus 2 (A)
T-minus 3 (B)
T-minus 1 (A)
T-minus 2 (B)
T-minus 1 (B)

Async and Await (python 3.5+)

async def defines a function as asynchronous (replaces @asyncio.coroutine)

  • can't contain yield, only await and return
  • this makes a function a coroutine

await keyword replaces yield from and is there to wait for a coroutine to finish

  • can only be within an async def function
  • calling await on an object means it has to be an awaitable object
    • i.e. it defines an __await__() method which returns an iterator which is not a coroutine itself

In [15]:
import asyncio

# Borrowed from http://curio.readthedocs.org/en/latest/tutorial.html.
async def countdown(number, n):
    while n > 0:
        print('T-minus', n, '({})'.format(number))
        await asyncio.sleep(1)
        n -= 1

asyncio.set_event_loop(asyncio.new_event_loop())
loop = asyncio.get_event_loop()

tasks = [asyncio.ensure_future(countdown("A", 2)), asyncio.ensure_future(countdown("B", 3))]
loop.run_until_complete(asyncio.wait(tasks))

loop.close()


T-minus 2 (A)
T-minus 3 (B)
T-minus 1 (A)
T-minus 2 (B)
T-minus 1 (B)