def gen_even_fibonacci(last):
    a = 0
    b = 1
    while True:
        c = a + b
        a = b
        b = c
        if b > last:
            break
        if b % 2 == 0:
            yield b

[i for i in gen_even_fibonacci(1000)]

"""
Explanation: Here's the old style generator that did everything in one generator.
End of explanation
"""


def gen_fibonacci():
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield b

def gen_even(gen):
    for i in gen:
        if i % 2 == 0:
            yield i
                

def gen_lte(gen, n):
    for i in gen:
        if i > n:
            break
        yield i

"""
Explanation: Now let's be more modular with more generators, where each generator does one thing well, and they can be combined with other generators. Each one is simple and easy to understand. This is like the Unix philosophy.
End of explanation
"""


[i for i in gen_lte(gen_even(gen_fibonacci()), 1000)]

[i for i in gen_lte(gen_fibonacci(), 1000)]

evens = (i for i in gen_fibonacci() if i%2 == 0)
[i for i in gen_lte(evens, 1000)]

evens = gen_even(gen_fibonacci())
[i for i in gen_lte(evens, 1000)]

[i for i in gen_lte(gen_even(gen_fibonacci()), 1000)]

"""
Explanation: Now we get to the benefit. We can combine generators much like one combines Linux shell commands with pipes on the command line.
End of explanation
"""


def gen_n(gen, n):
    for i in gen:
        if n <= 0:
            break
        yield i
        n -= 1

[i for i in gen_n(gen_fibonacci(), 10)]

[i for i in gen_n(gen_even(gen_fibonacci()), 10)]

fibs = gen_fibonacci()
evens = gen_even(fibs)
lte = gen_lte(evens, 100)
[i for i in lte]

"""
Explanation: It's easy to create new generators that can be combined with others.
End of explanation
"""


