A generator is a function that returns an object (iterator) which can be iterated over (one value at a time).. In other words, a generator is a statefull (with memory) function that for a sequence of identical calls produces a sequence of different results. Generators are usually used to implement iterators, when we don't want to store all the generated values in memory.
In [12]:
def yrange(max_i):
i = 0
while i < max_i:
yield i # A special return that continues with the next() call.
i += 1 # next() continues here.
In [13]:
for i in yrange(10):
print(i, end=' ')
In [57]:
# https://es.wikipedia.org/wiki/Sucesi%C3%B3n_de_Fibonacci
# https://www.mathsisfun.com/numbers/fibonacci-sequence.htm
def fib(n):
i = 0
a, b = 0, 1
while i < n:
yield a
a, b = b, a+b
i += 1
for i in fib(10):
print(i, end=' ')
Generator expressions can be considered as memory efficient generalization of list comprehensions, and also as compact representations of simple generators.
In [38]:
list_comprehension = [x*x for x in range(100)]
print(list_comprehension)
In [24]:
import sys
sys.getsizeof(list_comprehension)
Out[24]:
In [25]:
generator_expression = (x*x for x in range(100))
generator_expression
Out[25]:
In [26]:
sys.getsizeof(generator_expression)
Out[26]:
A generator expression can be faster than a list comprehension when the list does not fit in the cache.
In [33]:
%timeit sum([x*x for x in range(10)])
In [31]:
%timeit sum([x*x for x in range(10000000)])
In [34]:
%timeit sum(x*x for x in range(10))
In [32]:
%timeit sum(x*x for x in range(10000000))
In [58]:
for x in (i*2 for i in range(10)):
print(x, end=' ')
In [62]:
import time
c = 0
now = time.time()
# Notice that this is a memoryless process whilst list compressions produce lists.
for i in (x for x in range(2, 2000) if all(x % y != 0 for y in range(2, int(x ** 0.5) + 1))):
c += 1
print(i, end=' ')
print('\n{} primes found in {} seconds'.format(c,time.time() - now))
In [52]:
powers_of_two_generator_expression = (x**2 for x in range(10))
print(next(powers_of_two_generator_expression))
print(next(powers_of_two_generator_expression))
print(next(powers_of_two_generator_expression))
In [53]:
def generate_powers_of_two(exp):
for x in exp:
yield x**2
g = generate_powers_of_two(iter(range(10)))
print(next(g))
print(next(g))
print(next(g))
In [66]:
powers_of_myself_generator_expression = (x**x for x in range(10))
In [67]:
for i in powers_of_myself_generator_expression:
print(i)
In [68]:
def generate_powers_of_myself(exp):
for x in exp:
yield x**x
for i in generate_powers_of_myself(iter(range(10))):
print(i)
Coroutines can be classified as generators that consume data (and, as expected, generate some data). Equivalently, generators (and generator expression) can be considered as coroutines that does not consume data.
In [69]:
def minimize():
current = yield
while True:
value = yield current # Receives "value" and returns "current"
current = min(value, current)
it = minimize()
next(it) # "Prime" the coroutine (neccesary to reach the second yield).
print(it.send(10))
print(it.send(4))
print(it.send(22))
print(it.send(-1))