75

I have a function which yields results as it downloads them. For the purposes of this question, lets say I yield a sting once every second but I want a convenience function to wrap my generator:

import time

def GeneratorFunction(max_val):
    for i in range(0,5):
        time.sleep(1)
        yield "String %d"%i

def SmallGenerator():
    yield GeneratorFunction(3)

for s in SmallGenerator():
    print s

...why doesn't that just print the 5 strings I'm expecting? Instead it appears to return the generator functio:

<generator object GeneratorFunction at 0x020649B8>

How can I get this to yield the strings as a normal generator function would?

martineau
  • 119,623
  • 25
  • 170
  • 301
Jon Cage
  • 36,366
  • 38
  • 137
  • 215

5 Answers5

69

You may have to use the new yield from, available since Python 3.3, known as “delegated generator”.

If I understood the question correctly, I came to the same issue, and found an answer elsewhere.

I wanted to do something like this:

def f():

    def g():

        do_something()
        yield x
        …
        yield y

    do_some_other_thing()
    yield a
    …
    g()  # Was not working.
    yield g()  # Was not what was expected neither; yielded None.
    …
    yield b

I now use this instead:

yield from g()  # Now it works, it yields x and Y.

I got the answer from this page: Python 3: Using "yield from" in Generators - Part 1 (simeonvisser.com).

Hibou57
  • 6,870
  • 6
  • 52
  • 56
40

Can't believe I missed this; The answer is to simply return the generator function with suitable arguments applied:

import time

def GeneratorFunction(max_val):
    for i in range(0,max_val):
        time.sleep(1)
        yield "String %d"%i

def SmallGenerator():
    return GeneratorFunction(3) # <-- note the use of return instead of yield

for s in SmallGenerator():
    print s
Community
  • 1
  • 1
Jon Cage
  • 36,366
  • 38
  • 137
  • 215
  • 3
    unless your function reads: yield GeneratorFunction(3) yield GeneratorFunction(2). In this case a return would not work. Yield From fulfills this requirement. – DonkeyKong Sep 15 '19 at 21:06
  • 3
    but if you are using `return` inside `def SmallGenerator():`, it is not a nested generator function anymore?? or is it?? – Bikash Gyawali Oct 07 '20 at 15:52
5

Came here looking for a different form of "nested yield" and finally found the hidden answer. Might not be the best but it works.

I was wanting to yield through a registry tree and here is the solution.

        def genKeys(key):
            for value in key.values():
                yield value
            for subkey in key.subkeys():
                print(subkey)
                for x in genKeys(subkey): #this is the trick
                    continue
ben
  • 51
  • 1
  • 1
  • 1
    Looks like this yields the top-level values, and recursively prints subkeys, but doesn't recursively yield anything. Or did I miss something? – krubo Oct 21 '19 at 19:42
1

Here's another small example for generating the multiplication table from 1 to 10:

class Gen1:

    def __init__(self, gen2):
        self.gen2 = gen2

    def __iter__(self):    
        for a in range(1, 11):    
            for b in self.gen2:
                yield a * b


class Gen2:    

    def __iter__(self):
        for a in range(1, 11):
            yield a


def main():

    gen2 = Gen2()
    gen1 = Gen1(gen2)

    for v in gen1:
        print(v)

if __name__ == '__main__':
    main()
Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
1

I came looking for another use of nested yields..

list_of_lists = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]

def iter_all(foo):
    yield foo

    if isinstance(foo, list):
        for i in foo:
            for x in iter_all(i):
                yield x


print([i for i in iter_all(list_of_lists)])

output:

[[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [1, 2, 3], 1, 2, 3, [4, 5, 6], 4, 5, 6, [7, 8, 9], 7, 8, 9]
Ben
  • 275
  • 3
  • 12