How does "yield from" work in recursive functions?

Viewed 355

I'm writing some python code where I need to use generators inside recursive functions. Here is some code I wrote to mimic what I am trying to do. This is attempt 1.

def f():
    def f2(i):
        if i > 0:
            yield i
            f2(i - 1)
        
    yield f2(10)

for x in f():
    for y in x:
        print(y)

This only prints 10, attempt 2 using this yield from construct I found online.

def f():
    def f2(i):
        if i > 0:
            yield i
            yield from f2(i - 1)
        
    yield from f2(10)


for x in f():
    print(x)

This does what I want, but I don't understand what is happening, what is yield from doing behind the scenes and why doesn't my first attempt work?

2 Answers

You can think of yield from as a for loop which yields every item:

for i in f(10):
    yield i

is the same as yield from f(10). In other words, it yields the items from the given iteratable which in this case is another generator.

yield from g() will recurse inside a new generator g yielding from each yield statement at that generator

so

def g1():
    yield from g2()

def g2()
    for i in range(10):
        yield i * 2

You can think as if yield from in g1 was unrolling g2 inside of it, expanding to something like this

def g1():
    for i in range(10):
        yield i * 2

This not what is happening because you have scopes and etc, but during the execution of yield from g2() in g1 then interpreter recurse in g2 yield each value that it yields, possibly recursing to another generator.

Now consider this generator

def flatten(maybe_it): 
    try: 
        for i0 in maybe_it: 
            for i1 in flatten(i0): 
                yield i1 
    except TypeError: 
        yield maybe_it 

with yield from it can be rewrite as

def flatten(maybe_it): 
    try: 
        for i0 in maybe_it: 
            yield from flatten(i0): 
    except TypeError: 
        yield maybe_it
Related