2

First, consider the following code (I'm going to discuss several versions of subgen() next):

>>> def maingen(i):
...    print("maingen started")
...    yield from subgen(i)
...    print("maingen finished")
...
>>> for i in maingen(5):
...     print(i)
...     

I want to write several subgen generator functions.

A normal one is:

>>> def subgen_1(i):
...     yield i + 1
...     yield i + 2
...     yield i + 3

No prob, output is as expected:

maingen started
6
7
8
maingen finished

Now, I want an other version of subgen which yields nothing...

If I try that:

>>> def subgen_2(i):
...     i * 3

I've an exception:

maingen started
Traceback (most recent call last):
  ...
TypeError: 'NoneType' object is not iterable

It's expected: subgen_2 isn't a generator function.

OK, next. I can read somewhere (like the first answer here) that I've to raise a StopIteration. (Note that, as a newbie, I can't comment this answer.)

>>> def subgen_3(i):
...     i * 3
...     raise StopIteration()
...     

As identified in PEP 479, "Finally, the proposal also clears up the confusion about how to terminate a generator: the proper way is return, not raise StopIteration.", I only get:

maingen started

(no maingen finished...)

And the only way I've found to get things OK is:

>>> def subgen_4(i):
...     i * 3
...     return
...     yield
... 

With this, I get:

maingen started
maingen finished

Hourrah! But this solution isn't beautiful...

Does anyone have a better or a more pythonic idea?

Is it possible to write a decorator to secretly add the ugly yield statement?

4
  • a function could return a generator without necessarily being a generator itself, you could just return iter([]) if you want the function to run and then result in an empty iterator. Commented May 18, 2016 at 15:19
  • if subgen_2 does not actually yield anything why do you use yield from subgen_2() in the first place? Commented May 18, 2016 at 15:22
  • That answer you linked to only mentioned raising a StopIteration to stop the generator (without yielding anything first). It does not change the fact though, that you need to have a yield statement in the function body (syntactically), to make it a generator function. Commented May 18, 2016 at 15:23
  • 1
    use this answer or poke's idea of yield from [], in any case you are asking how to make an empty generator which is exactly the same as yield break in Python. Commented May 18, 2016 at 15:38

2 Answers 2

2

If you want a sub generator to yield nothing, then return an empty iterator:

def subgen_2(i):
    i * 3
    return iter([]) #empty iterator

The reason you were getting TypeError: 'NoneType' object is not iterable is because without a yield statement in your sub-generator it was not a generator, but a regular function that implicitly returned None.

By returning a valid iterator that doesn't produce any values you will be able to yield from subgen_2() without error and without generating any additional values.

Another way to hide this (I don't really see why you would want to) is to make a decorator that literally just calls your function then does return iter(()) or yield from [].

def gen_nothing(f):
    @functools.wraps(f)
    def wrapper(*args,**kw):
        f(*args,**kw)
        yield from []
    return wrapper

But the only difference this produces is that the stack will require one additional frame for the wrapper, which means your Traceback messages will have a bit more noise:

Traceback (most recent call last):
  File "/Users/Tadhg/Documents/codes/test.py", line 15, in <module>
    for i in subgen():
  File "/Users/Tadhg/Documents/codes/test.py", line 6, in wrapper
    f(*args,**kw)
  File "/Users/Tadhg/Documents/codes/test.py", line 12, in subgen
    3/0
ZeroDivisionError: division by zero
Sign up to request clarification or add additional context in comments.

4 Comments

Since return iter([]) does not turn the function into a generator itself, and you might prefer that, you can use something like yield from [] to do the equivalent thing and produce a generator function instead.
I was asking for a more beautiful way... The yield from [] solution is a good one.
But I was also asking if it's possible to write a decorator to hide that "fake" statement...
Thanks. I'm not a fan too... And I just posted an answer to show how I prefer to rewrite the maingen generator function.
0

After reading all comments, I think the more pythonic way is to rewrite the maingen:

def maingen(i):
    print("maingen started")
    gen = subgen(i)
    if gen:
        yield from gen
    print("maingen finished")

With that, a subgen which is really a generator function:

def subgen_1(i):
    yield i + 1
    yield i + 2
    yield i + 3

or a subgen which is a simple function:

def subgen_2(i):
    i * 3

are both "accepted" when "injected" in the maingen generator function, and don't need some ugly yield statements.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.