简体   繁体   中英

Python unexpected StopIteration

This is my code

class A:
    pass

def f():
    yield A()

def g():
    it = f()
    next(it).a = next(it, None)

g()

that produces the StopIteration error, caused by next(it).a = next(it, None) . Why?

The documentation says that next function does not raise the StopIteration if the default value is provided, and I expected it to get me the first item from the generator (the A instance) and set the a attribute to None .

Because f only yields a single value, you can only call next on it once.

The right hand side of your expression ( next(it, None) ) is evaluated before the left hand side, and thus exhausts the generator.

Calling next(it).a on the left hand side will then raise StopIteration .

Your f() generator function yields just one value. After that it is exhausted and raises StopIteration .

>>> class A:
...     pass
... 
>>> def f():
...     yield A()
... 
>>> generator = f()
>>> generator
<generator object f at 0x10be771f8>
>>> next(generator)
<__main__.A object at 0x10be76f60>
>>> next(generator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

That's because there is no loop in f() to yield more than once, a generator function does not, on its own, loop, just because you can use it in a loop.

Note that for an assignment, Python executes the right-hand-side expression first , before figuring out what to assign it to . So the next(it, None) is called first, the next(it).a for the assignment is called second .

The body of the f() function is executed just like any other Python function, with the addition of pausing . next() on the generator un-pauses the code, and the code then runs until the next yield statement is executed. That statement then pauses the generator again. If the function instead ends (returns), StopIteration is raised.

In your f() generator that means:

  1. when you call f() a new generator object is created. The function body is paused.
  2. you call next() on it the first time. The code starts running, creates an instance of A() and yields that instance. The function is paused again.
  3. you call next() on it a second time. The code starts running, reaches the end of the function, and returns. StopIteration is raised.

If you add a loop to f() , or simply add a second yield line, your code works:

def f():
    yield A()
    yield A()

or

def f():
    while True:
        yield A()

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