python
generators
a function that contains a yield statement is called a generator function
def fib():
    a, b = 0, 1
    while True:
        yield b
        a, b = b, a + b
fib
a generator function returns a generator object
g = fib()
type(g)
to get the values that the generator yields use next
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
you can also define finite generators:
def first_two_primes():
    yield 2
    yield 3
g = first_two_primes()
print(next(g))
print(next(g))
exhausting a generator raises the StopIteration exception:
print(next(g))
how does it work?
- generator functions are calculated lazily
 - the function is first executed after 
nextis first applied on the generator object yieldis a sequencer - after ayieldstatement, execution jumps back to the statement afternext
- Python creates a closure around generator functions
 - all local variables and globals used in the generator function are stored in the closure
 - the closure isn’t collected by the GC until the generator is exhausted
 
send
yieldis ,in fact, a command expression- it can be used to receive values from outside the generator using 
send 
def echoer():
    last = -1
    while True:
        last = (yield last) * 2
g = echoer()
next(g)
is -1, because it yields last, which is -1 initially
now,
g.send(5)
returns 10 - as yield last evaluates to 5, which is then multiplied by 2 and yielded in the next iteration
send(x)resumes execution of the generator function- the 
yieldexpression evaluates tox - the function runs until the next 
yieldand so on 
throw
throwthrows an exception stacktraced as if it was thrown in the lastyieldexecuted- it does not resume execution of the function.
 - it exhausts the generator
 
example:
def thrower():
    while True:
        print("x")
        yield 5
thrower
as we saw before,
g = thrower()
value = next(g)
places the value 5 in value and prints x.
however,
g.throw(ValueError())
causes a ValueError with a stacktrace that refers to the yield that already occurred without printing x!
if next(g) was never called before throwing, the exception will be stacktraced to line 1:
g = thrower()
g.throw(ValueError())
exercises
exercise 1
create a generator function returning all prime numbers.
def primes():
    pass
solution:
def primes():
    last = 1
    while True:
        last += 1
        for i in range(2, last):
            if last % i == 0:
                break
        else:
            yield last
exercise 2
create a generator function for the Fibonacci sequence. the generator can receive a pair of integers, and when it does, it restarts the sequence with the values as the new initial values. (example in next slide)
example:
g = new_fib()
next(g) # returns 1
next(g) # returns 1
next(g) # returns 2
next(g) # returns 3
# ...
g.send((1, 2)) # returns 1
next(g) # returns 2
next(g) # returns 3
next(g) # returns 5
...
solution:
def new_fib():
    a = 0
    b = 1
    while True:
        pair = yield b
        if pair != None:
            a, b = pair
            a, b = b - a, a
        else:
            a, b = b, a + b