Generators Comprehension Iterators

Generators


In [ ]:
from random import randrange

def randomly_generate_random_nonmultiples_of_ten_between_0_and_99():
    while True:
        digit = randrange(0, 100)
        if digit % 10:
            yield digit
        else:
            return

generator = randomly_generate_random_nonmultiples_of_ten_between_0_and_99()
while True:
    try:
        # Alternative: print(generator.__next__())
        print(next(generator))
    except StopIteration:
        print('A multiple of 10 was generated, which ended the process')
        break

In [ ]:
def generate_multiples_of(n):
    for i in range(1, 10):
        yield n * i
        
def generate_multiples():
    for n in range(1, 10):
        yield from generate_multiples_of(n)

count = 0
generator = generate_multiples()
for e in generator:
    print('{:3d}'.format(e), end = '')
    count += 1
    if count % 9 == 0:
        print()

In [ ]:
from random import randint

def randomly_announce_even_or_odd():
    while True:
        if randint(0, 1):
            print('It is odd.')
        else:
            print('It is even.')
        yield

generator = randomly_announce_even_or_odd()
for i in range(10):
    next(generator)

In [ ]:
def yield_and_send():
    sent = None
    i = 0
    while True:
        if sent is None:
            # "i" is generated.
            # - If a call to "__next__()" follows,
            #   "sent" then receives the value None
            #    and this statement will be executed again after i has been incremented.
            # - If a call to "send()" follows,
            #   "sent" then receives the value passed as an argument to "send()"
            #   and the body of the "elif" or "else" parts will be executed
            #   after i has been incremented.
            sent = yield i
        elif sent == 'stop':
            return
        else:
            # Same as above, except that it is "sent + i" that is generated.
            sent = yield sent + i
        i += 1

generator = yield_and_send()
# First "next()" has to be issued, "send()" cannot be issued.
print(next(generator))
# Now either "next()" or "send()" can be issued:
# "yield i" has last been executed; 
# which of "__next__()" or "send()" will be called
# will determine the value that "sent" will receive.
print(next(generator))
print(next(generator))
print(generator.send(10))
print(generator.send(20))
print(next(generator))
print(generator.send(30))
print(next(generator))
print(next(generator))
try:
    print(generator.send('stop'))
except StopIteration:
    print('Generation has been stopped')

Comprehension and generator expressions


In [ ]:
# More generally, any iterable.
L = [0, 3, 2, 5, 1]

# List comprehension
print([i * i for i in L])
print([i * i for i in L if i % 2])
print()
# Set comprehension
print({i * i for i in L})
print({i * i for i in L if i % 2})
print()
# Dictionnary comprehension
print({i: i * i for i in L})
print({i: i * i for i in L if i % 2})
print()

# "print((i: i * i for i in L))" and "print((i: i * i for i in L if i % 2)" are invalid:
# there is no tuple comprehension.
# "(i: i * i for i in L)" and "(i: i * i for i in L if i % 2)" are generator expressions:
# the call to "tuple()" in "tuple((i: i * i for i in L))",
# the call to "tuple()" in "tuple((i: i * i for i in L if i % 2))"
# force the generator to produce all values;
# the inner parentheses (for "generator expression") can be omitted.
print(tuple(i * i for i in L))
print(tuple(i * i for i in L if i % 2))
print()
for e in (i * i for i in L):
    print(e, end = ' ')
print()
for e in (i * i for i in L if i % 2):
    print(e, end = ' ')
print('\n')

# List, set and dictionnary comprehension can be achieved with a similar technique:
print(list(i * i for i in L))
print(list(i * i for i in L if i % 2))
print(set(i * i for i in L))
print(set(i * i for i in L if i % 2))
print(dict((i, i * i) for i in L))
print(dict((i, i * i) for i in L if i % 2))
print()
# The parentheses for "generator expression" can be omitted here too:
print(sorted(i * i for i in L))
# But they cannot be omitted here:
print(sorted((i * i for i in L), reverse = True))

In [ ]:
M = [[[111, 112, 113],
      [121, 122, 123],
      [131, 132, 133]],
     [[211, 212, 213],
      [221, 222, 223],
      [231, 232, 233]]]

print([            2 * element
       for three_rows in M
           for row in three_rows
               for element in row])
print([   [   [    2 * element
               for element in row]
           for row in three_rows]
       for three_rows in M])
print()
print([            2 * element
       for three_rows in M
         if three_rows[0][0] // 100 % 2
           for row in three_rows
             if row[0] // 10 % 2
               for element in row
                 if element % 2])
print([   [   [    2 * element
               for element in row 
                 if element % 2]
           for row in three_rows 
             if row[0] // 10 % 2]
       for three_rows in M 
         if three_rows[0][0] // 100 % 2])

Iterators


In [ ]:
# More generally, any iterable.
R = range(5)

iterator_1 = iter(R)
print(iterator_1 is R)
print(next(iterator_1))
print(next(iterator_1))
iterator_2 = iter(R)
print(iterator_2 is iterator_1)
print(next(iterator_2))
print(next(iterator_1))
print(next(iterator_2))
print()

generator_expression = (e for e in R)

iterator_1 = iter(generator_expression)
print(iterator_1 is generator_expression)
print(next(iterator_1))
print(next(iterator_1))
iterator_2 = iter(generator_expression)
print(iterator_2 is iterator_1)
print(next(iterator_2))
print(next(iterator_1))
print(next(iterator_2))
print()

def generate_R_members():
    for e in R:
        yield e

generator = generate_R_members()
iterator_1 = iter(generator)
print(iterator_1 is generator)
print(next(iterator_1))
print(next(iterator_1))
iterator_2 = iter(generator)
print(iterator_2 is iterator_1)
print(next(iterator_2))
print(next(iterator_1))
print(next(iterator_2))