简体   繁体   中英

Why can't my except block catch a StopIteration exception?

What i want to do with this program is that I want it to return "yes" and "no" over and over again by using recursion. Please help? Am I doing something wrong?

def yes_or_no():
for x in ("yes","no"):
    try:
        yield x
    except (StopIteration):
        return yes_or_no()

gen =  yes_or_no()
print(next(gen))
print(next(gen))
print(next(gen))

As soon as it reaches the 3rd print, it says StopIteration even though I thought I caught it in Error Handling?

The StopIteration exception is not raised in the yield call, so you won't be catching it with that try/except setup.

By having a yield statement in your function, you have turned it into a generator (which you seem to understand). Each time the generator is evaluated, it will re-enter at the yield where it finished last time, and carry on until the next yield or the function completes. So on your third next() , execution will resume at the yield , get back to the for loop, see that it is finished, and carry on after it, which is simply the end of the function in this case. The function will return, and thus the generator will raise its StopIteration.

I don't recommend you use recursion for this task; just use an outer infinite loop around your ("yes, "no") loop (or better yet, something from itertools).

If you really want to use recursion, then you might want to try

def yes_or_no():
    for x in ("yes", "no"):
        yield x
    yield from yes_or_no()

The yield from bit requires Python >= 3.3 I think.

yield x inside the loop will never raise StopIteration , so you cannot catch it. The Python for loop is implemented using StopIteration , true, but the instance raised there is caught before you can deal with it .

The way to make what you have in mind work is to just... let the loop run its course, then yield the rest of the elements. Recursively:

def yes_or_no():
    for x in ("yes","no"):
        yield x
    yield from yes_or_no()

As an aside: yield from is what you need in order to continue yielding elements produced by the recursive calls. The return value from a generator doesn't help you yield more elements. Ironically, in fact, it sets up the exception that results for running the generator out of elements:

>>> def example():
...     yield 1
...     return 2
...
>>> x = example()
>>> next(x)
1
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: 2

(Of course, your entire goal is for yes_or_no() not to run out of elements, so.)

At any rate, using recursion for this is a bad idea, since you artificially limit how long it can work for (until you run out of stack frames). Just iterate:

def yes_or_no():
    while True:
        yield 'yes'
        yield 'no'

Or use itertools :

>>> import itertools
>>> x = itertools.cycle(('yes', 'no'))
>>> next(x)
'yes'
>>> next(x)
'no'
>>> next(x)
'yes'
>>> next(x)
'no'

So how do you make good use of StopIteration in user code? Either by explicitly raising it in an iterator implementation (although the most obvious ways to do this are again already done for you), or by catching it when you manually advance an iterator using next . Examples:

class SingletonIterator:
    """Proxy iterator to allow 'iteration' over a fictitious sequence
    holding a single element, the `value`."""
    def __init__(self, value):
        self._value = value
        self._yielded = False

    def __iter__(self):
        return self

    def __next__(self):
        if self._yielded:
            raise StopIteration
        self._yielded = True
        return self._value


def first(predicate, sequence):
    """The first element `e` of `sequence` satisfying `predicate(e)`.
    Raises ValueError for an empty sequence."""
    try:
        return next(e for e in sequence if predicate(e))
    except StopIteration:
        raise ValueError('empty sequence')

Here is a simple recursive solution:

def yes_or_no():
    yield from ('yes', 'no')
    yield from yes_or_no()

Edit:

The itertools module has the function cycle that takes in an iterator and returns a generator that cycles through its input.

import itertools
def yes_or_no():
    yield from itertools.cycle(("yes", "no"))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM