Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Generator case where nothing to return

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?

like image 630
whats canasta Avatar asked Jul 18 '13 01:07

whats canasta


Video Answer


2 Answers

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
like image 124
Wolph Avatar answered Sep 24 '22 22:09

Wolph


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
    
like image 21
Tadeck Avatar answered Sep 26 '22 22:09

Tadeck