12

I have a generator like so:

def iterate_my_objects_if_something(self):
    for x in self.my_objects:
        if x.something:
            yield x

Which I call like so:

for x in self.iterate_my_objects_if_something():
    pass

In the case where there is nothing to return, this tries to iterate over NoneType and throws an exception.

How do I return an empty generator instead?

Óscar López
  • 232,561
  • 37
  • 312
  • 386
whats canasta
  • 763
  • 2
  • 7
  • 16
  • 2
    Why is `my_objects` `None` when you don't have any objects? Why isn't it a list of no objects? – user2357112 Jul 18 '13 at 01:49
  • I think you might be misdiagnosing the problem. You should only get this error if `self.my_objects` is `None`, not if there are no objects yielded. – DSM Jul 18 '13 at 01:49
  • you should probably post the stack trace, my bet is that the exception is being thrown _within_ the generator. All you need to do is skip the loop if you can't iterate over `self.my_objects`, or catch the exception within the generator. – Shep Jul 18 '13 at 01:56
  • Turns out I was chaining this generator in a different generator and it was throwing an error there because this one returned none. Stack trace was confusing. – whats canasta Jul 18 '13 at 02:00

5 Answers5

8

Just do a simple check:

def iterate_my_objects_if_something(self):
    if self.my_objects:
        for x in self.my_objects:
            if x.something:
                yield x
Wolph
  • 78,177
  • 11
  • 137
  • 148
5

It is important to know, which iteration causes the error. That is certainly pointed in traceback, but in this case traceback is not necessary (keep reading).

Is iteration over generator an issue?

After you take a look at that, it is obvious, but worth clarifying that:

  • empty generator is not of NoneType, so iterating through it will not cause such issue:

    >>> def test_generator():
        for i in []:
            yield i
    
    
    >>> list(test_generator())  # proof it is empty
    []
    >>> for x in test_generator():
        pass
    
    >>> 
    
  • generator is recognized by Python during definition (I am simplifying) and trying to mix generators and simple functions (eg. by using conditional, as below) will be a syntax error:

    >>> def test_generator_2(sth):
        if sth:
            for i in []:
                yield i
        else:
            return []
    
    SyntaxError: 'return' with argument inside generator (<pyshell#73>, line 6)
    

Is the iteration inside generator an issue?

Based on the above the conclusion is that the error is not about iterating through iterator, but what happens when it is created (the code within generator):

def iterate_my_objects_if_something(self):
    for x in self.my_objects:  # <-- only iteration inside generator
        if x.something:
            yield x

So seemingly in some cases self.my_objects becomes None.

Solution

To fix that issue either:

  • guarantee that self.my_objects is always an iterable (eg. empty list []), or
  • check it before iteration:

    def iterate_my_objects_if_something(self):
        # checks, if value is None, otherwise assumes iterable:
        if self.my_objects is not None:
            for x in self.my_objects:
                if x.something:
                    yield x
    
Tadeck
  • 132,510
  • 28
  • 152
  • 198
3

Check before iterating:

if self.my_objects:
    for x in self.my_objects:
        if x.something:
          yield x
Óscar López
  • 232,561
  • 37
  • 312
  • 386
0

If there is nothing to iterate throw StopIteration exception. See the examples below.

def iterate_my_objects_if_something(self):
    if not self.my_objects:
        raise StopIteration
    for x in self.my_objects:
        if x.something:
            yield x

I'd go even further:

def iterate_my_objects_if_something(self):
    if not self.my_objects:
        raise StopIteration
    yield from (x for x in self.my_objects if x.something)
jonchar
  • 6,183
  • 1
  • 14
  • 19
Albert Tugushev
  • 1,504
  • 13
  • 25
0

This answer was answered in another question: What is the difference between raise StopIteration and a return statement in generators?.

Basically, you just need to return None to end your generator. Raising StopIteration is now deprecated.

So your code can simply be:

def iterate_my_objects_if_something(self):
    if self.my_objects == []:
        return
    for x in self.my_objects:
        if x.something:
            yield x

This is very close to the answer of this question, but it is just more explicit.

guhur
  • 2,500
  • 1
  • 23
  • 33