41

I want to write a Python generator function that never actually yields anything. Basically it's a "do-nothing" drop-in that can be used by other code which expects to call a generator (but doesn't always need results from it). So far I have this:

def empty_generator():
    # ... do some stuff, but don't yield anything
    if False:
        yield

Now, this works OK, but I'm wondering if there's a more expressive way to say the same thing, that is, declare a function to be a generator even if it never yields any value. The trick I've employed above is to show Python a yield statement inside my function, even though it is unreachable.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 2
    Out of curiosity, why does it have to be a generator? I can't imagine what sort of correct caller code could explicitly require a generator... – static_rtti Jun 07 '11 at 15:21
  • @static_rtti I've just discovered that fake generators work great for lazy loading purposes, depending on the context. – Liz Av Jul 17 '15 at 23:18
  • @Ekevoo could you link to an example? – static_rtti Jul 18 '15 at 08:35
  • 1
    @static_rtti Here. I'm sure there are better ways to do what I did, but it got the job done. https://github.com/ekevoo/hfbr/blob/d8d94d104ba35a5268887f889b8b68abce9c87cc/hfbrw.py#L154 – Liz Av Jul 18 '15 at 13:21
  • 1
    For the record, I would put the `if False: yield` at the _top_ of the function, so that it is _immediately_ obvious what you're doing. This is actually an advantage it has over the `return; yield` solution: either way this construct serves as something of an annotation declaring "I want this to be a generator", and we normally put annotations that effect the entire function at the top for good reason - it helps guide the reader. – mtraceur Aug 02 '21 at 21:59

3 Answers3

45

Another way is

def empty_generator():
    return
    yield

Not really "more expressive", but shorter. :)

Note that iter([]) or simply [] will do as well.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 4
    My vote goes to iter([]). [] is not really an iterator (you cannot call .next() on it etc.) – codeape Jun 07 '11 at 14:34
  • `iter([])` won't work in my case because I actually do a little work at the top of the function (not shown in my original post, sorry about that). I like the return-then-yield solution all right. – John Zwinck Jun 07 '11 at 14:52
  • 3
    @John: Be sure to add a comment explaining what it's doing! :) – Sven Marnach Jun 07 '11 at 15:05
  • 3
    That's strange, because normally **return** an **yield** can't be together in a function definition code. But I noticed that if **return** is replaced with **return 25**, a syntax error is raised: _'return' with argument inside generator_ . So there is a difference between return with and return without argument. What is this difference in fact ? – eyquem Jun 07 '11 at 17:01
  • 6
    @eyquem: `return` in a generator is the same as flow reaching the end of the code -- it will make the generator drop it's frame and raise `StopIteration`. It doesn't make sense to return a value, just as flow reaching the end of code does not return a value. – Sven Marnach Jun 07 '11 at 17:49
  • 2
    Note that it's now valid: [PEP 380](http://docs.python.org/dev/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator). – Matt Joiner Jan 17 '12 at 14:35
38

An even shorter solution:

def empty_generator():
  yield from []
AmirHossein
  • 1,310
  • 1
  • 12
  • 19
  • 2
    Interresting. For Python 3 though. – Jerther Mar 16 '17 at 12:04
  • Most elegant and syntax checker friendly in my opinion – majkelx Jun 19 '20 at 16:30
  • 4
    Suggest using tuple literal `()` instead of list literal `[]` if you want to help Python avoid the overhead of empty list creation (depending on how "smart" the Python implementation is, it might conceivably be able to recognize that you never use the list, and optimize accordingly to the point that it's equivalent, but `()` is just guaranteed immutable by the language, and it is normal and expected that `[] is not []` but `() is ()`, and so it takes substantially less cleverness and effort for a Python implementation to make `()` effectively free versus getting `[]` to be anywhere near free). – mtraceur Aug 02 '21 at 21:55
1

For maximum readability and maintainability, I would prioritize a construct which goes at the top of the function. So either

  1. your original if False: yield construct, but hoisted to the very first line, or
  2. a separate decorator which adds generator behavior to a non-generator callable.

(That's assuming you didn't just need a callable which did something and then returned an empty iterable/iterator. If so then you could just use a regular function and return ()/return iter(()) at the end.)

Imagine the reader of your code sees:

def name_fitting_what_the_function_does():
    # We need this function to be an empty generator:
    if False: yield

    # that crucial stuff that this function exists to do

Having this at the top immediately cues in every reader of this function to this detail, which affects the whole function - affects the expectations and interpretations of this function's behavior and usage.

How long is your function body? More than a couple lines? Then as a reader, I will feel righteous fury and condemnation towards the author if I don't get a cue that this function is a generator until the very end, because I will probably have spent significant mental cost weaving a model in my head based on the assumption that this is a regular function - the first yield in a generator should ideally be immediately visible, when you don't even know to look for it.

Also, in a function longer than a few lines, a construct at the very beginning of the function is more trustworthy - I can trust that anyone who has looked at a function has probably seen its first line every time they looked at it. That means a higher chance that if that line was mistaken or broken, someone would have spotted it. That means I can be less vigilant for the possibility that this whole thing is actually broken but being used in a way that makes the breakage non-obvious.

If you're working with people who are sufficiently fluently familiar with the workings of Python, you could even leave off that comment, because to someone who immediately remembers that yield is what makes Python turn a function into a generator, it is obvious that this is the effect, and probably the intent since there is no other reason for correct code to have a non-executed yield.

Alternatively, you could go the decorator route:

@generator_that_yields_nothing
def name_fitting_what_the_function_does():
    # that crucial stuff for which this exists


def generator_that_yields_nothing(wrapped):
    @functools.wraps(wrapped)
    def wrapper_generator():
        if False: yield
        wrapped()
    return wrapper_generator
mtraceur
  • 3,254
  • 24
  • 33