stop-iteration-return / R1708#

Message emitted:

Do not raise StopIteration in generator, use return statement instead

Description:

According to PEP479, the raise of StopIteration to end the loop of a generator may lead to hard to find bugs. This PEP specify that raise StopIteration has to be replaced by a simple return statement

Problematic code:

fruit_generator.py:

def fruit_generator():
    for fruit in ["apple", "banana"]:
        yield fruit
    raise StopIteration  # [stop-iteration-return]

two_fruit_generator.py:

def two_fruits_generator(fruits):
    for fruit in fruits:
        yield fruit, next(fruits)  # [stop-iteration-return]

two_good_fruit_generator.py:

def two_good_fruits_generator(fruits):
    for fruit in fruits:
        if not fruit.is_tasty():
            continue
        while True:
            next_fruit = next(fruits)  # [stop-iteration-return]
            if next_fruit.is_tasty():
                yield fruit, next_fruit
                break

Correct code:

fruit_generator.py:

def fruit_generator():
    """The example is simple enough you don't need an explicit return."""
    for fruit in ["apple", "banana"]:
        yield fruit

two_fruit_generator.py:

def two_fruits_generator(fruits):
    """Catching the StopIteration."""
    for fruit in fruits:
        try:
            yield fruit, next(fruits)
        except StopIteration:
            print("Sorry there is only one fruit left.")
            yield fruit, None

two_good_fruit_generator.py:

def two_good_fruits_generator(fruits):
    """A return can be used to end the iterator early, but not a StopIteration."""
    for fruit in fruits:
        if not fruit.is_tasty():
            continue
        while True:
            next_fruit = next(fruits, None)
            if next_fruit is None:
                print("Sorry there is only one fruit left.")
                yield fruit, None
                # We reached the end of the 'fruits' generator but raising a
                # StopIteration instead of returning would create a RuntimeError
                return
            if next_fruit.is_tasty():
                yield fruit, next_fruit
                break

Additional details:

It's possible to give a default value to next or catch the StopIteration, or return directly. A StopIteration cannot be propagated from a generator.

Related links:

Created by the refactoring checker.