#!# ============
#!#  Generators
#!# ============
#!#
#!# This page contains a memo on generators.
#!#
#!# For a complete reference documentation, look at
#!# https://docs.python.org/3/reference/expressions.html#generator-expressions

####################################################################################################

from Tools import *

import asyncio

####################################################################################################

#!# List Comprehensions
#!# -------------------

a_list = [x for x in range(10)]
print(a_list)
#o#

a_set = {x % 10 for x in range(100)}
print(a_set)
#o#

a_dict = {x:x for x in range(5)}
print(a_dict)
#o#

#!# Nested List Comprehensions
#!# ~~~~~~~~~~~~~~~~~~~~~~~~~~

a_list_of_list = [[x, y]
                  for x in range(2)
                  for y in range(4)]
print(a_list_of_list)
#o#

#!# Filtered List Comprehensions
#!# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

a_list = [x for x in range(10)
          if 2 <= x <= 6]
print(a_list)
#o#

a_list_of_list = [[x, y]
                  for x in range(10)
                  for y in range(10)
                  if 2 <= x <= 3 and 5 <= y <= 6]
print(a_list_of_list)
#o#

####################################################################################################

#!# Generator expressions
#!# ---------------------

N = 4
multiplication_table = (x*y for x in range(1, N+1) for y in range(1, N+1))

print(list(multiplication_table))
#o#

#!# Yield expressions
#!# -----------------

def simplist_generator():
    yield 1

print(list(simplist_generator()))
#o#

def range_clone(N):
    for i in range(N):
        yield i

print(list(range_clone(10)))
#o#

#!# A generator exit when :code:`StopIteration` is raised:

def generator(N, M):
    value = N
    while True:
        yield value
        value += 1
        if M <= value:
            raise StopIteration

print(list(generator(10, 20)))
#o#

#!# We can yield from a generator instead of a value:

def generator(N):
    yield from range_clone(N)

print(list(generator(10)))
#o#

#!# Generator-iterator Methods
#!# --------------------------

def echo(value=None):
    print("echo is running")
    try:
        while True:
            try:
                value = (yield value)
            except Exception as exception:
                print('exception:', exception)
                value = exception
    finally:
        # cleanup code
        print("reached finally")

generator = echo(1)
#o#

#!# Starts the execution of a generator function or resumes it at the last executed yield expression.

print(next(generator))
#o#

print(next(generator))
#o#

#!# Resumes the execution and “sends” a value into the generator function.

print(generator.send(2))
#o#

#!# Raises an exception of type type at the point where the generator was paused.

# generator.throw(type[, value[, traceback]])
generator.throw(TypeError, "spam")
#o#

#!# Raises a :code:`GeneratorExit` at the point where the generator function was paused.

generator.close()
#o#

#!# Asynchronous Generator Functions
#!# --------------------------------

#!# **To be completed**
#!#
#!# For further information see `PEP 525 -- Asynchronous Generators <https://www.python.org/dev/peps/pep-0525>`_

async def ticker(delay, to):
    """Yield numbers from 0 to `to` every `delay` seconds."""
    for i in range(to):
        yield i
        await asyncio.sleep(delay)

#!# Asynchronous Generator-Iterator Methods
#!# ---------------------------------------

#!# **To be completed**

3.3.8. Generators

This page contains a memo on generators.

For a complete reference documentation, look at https://docs.python.org/3/reference/expressions.html#generator-expressions

from Tools import *

import asyncio

3.3.8.1. List Comprehensions

a_list = [x for x in range(10)]
print(a_list)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a_set = {x % 10 for x in range(100)}
print(a_set)
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
a_dict = {x:x for x in range(5)}
print(a_dict)
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}

3.3.8.1.1. Nested List Comprehensions

a_list_of_list = [[x, y]
                  for x in range(2)
                  for y in range(4)]
print(a_list_of_list)
[[0, 0], [0, 1], [0, 2], [0, 3], [1, 0], [1, 1], [1, 2], [1, 3]]

3.3.8.1.2. Filtered List Comprehensions

a_list = [x for x in range(10)
          if 2 <= x <= 6]
print(a_list)
[2, 3, 4, 5, 6]
a_list_of_list = [[x, y]
                  for x in range(10)
                  for y in range(10)
                  if 2 <= x <= 3 and 5 <= y <= 6]
print(a_list_of_list)
[[2, 5], [2, 6], [3, 5], [3, 6]]

3.3.8.2. Generator expressions

N = 4
multiplication_table = (x*y for x in range(1, N+1) for y in range(1, N+1))

print(list(multiplication_table))
[1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, 4, 8, 12, 16]

3.3.8.3. Yield expressions

def simplist_generator():
    yield 1

print(list(simplist_generator()))
[1]
def range_clone(N):
    for i in range(N):
        yield i

print(list(range_clone(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

A generator exit when StopIteration is raised:

def generator(N, M):
    value = N
    while True:
        yield value
        value += 1
        if M <= value:
            raise StopIteration

print(list(generator(10, 20)))
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
    /home/opt/python-virtual-env/py36/lib/python3.6/site-packages/ipykernel_launcher.py:9: DeprecationWarning: generator 'generator' raised StopIteration
  if __name__ == '__main__':

We can yield from a generator instead of a value:

def generator(N):
    yield from range_clone(N)

print(list(generator(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

3.3.8.4. Generator-iterator Methods

def echo(value=None):
    print("echo is running")
    try:
        while True:
            try:
                value = (yield value)
            except Exception as exception:
                print('exception:', exception)
                value = exception
    finally:
        # cleanup code
        print("reached finally")

generator = echo(1)

Starts the execution of a generator function or resumes it at the last executed yield expression.

print(next(generator))
echo is running
1
print(next(generator))
None

Resumes the execution and “sends” a value into the generator function.

print(generator.send(2))
2

Raises an exception of type type at the point where the generator was paused.

# generator.throw(type[, value[, traceback]])
generator.throw(TypeError, "spam")
exception: spam

Raises a GeneratorExit at the point where the generator function was paused.

generator.close()
reached finally

3.3.8.5. Asynchronous Generator Functions

To be completed

For further information see PEP 525 – Asynchronous Generators

async def ticker(delay, to):
    """Yield numbers from 0 to `to` every `delay` seconds."""
    for i in range(to):
        yield i
        await asyncio.sleep(delay)

3.3.8.6. Asynchronous Generator-Iterator Methods

To be completed