yield expression
yield_atom ::= “(” yield_expression “)”
yield_expression ::= “yield” [expression_list | “from” expression]
- 1
The yield expression is used when defining a generator function or an asynchronous generator function and thus can only be used in the body of a function definition. Using a yield expression in a function’s body causes that function to be a generator, and using it in an async def
function’s body causes that coroutine function to be an asynchronous generator.
- 2
- 1
When a generator function is called, it returns an iterator known as a generator. That generator then controls the execution of the generator function. - 2
The execution starts when one of the generator’s methods is called. At that time, the execution proceeds to the first yield expression, where it is suspended again, returning the value ofexpression_list
to the generator’s caller. By suspended, we mean that all local state is retained, including the current bindings of local variables, the instruction pointer, the internal evaluation stack, and the state of any exception handling. - 3
When the execution is resumed by calling one of the generator’s methods, the function can proceed exactly as if the yield expression were just another external call. The value of the yield expression after resuming depends on the method which resumed the execution. If__next__()
is used (typically via either afor
or thenext()
builtin) then the result isNone
. Otherwise, ifsend()
is used, then the result will be the value passed in to that method.
- 1
- 3 generator v.s. coroutine
The only difference is that a generator function cannot control where the execution should continue after it yields; the control is always transferred to the generator’s caller.
- 4 Generator-iterator methods
generator.__next__()
Starts the execution of a generator function or resumes it at the last executedyield expression
. When a generator function is resumed with a__next__()
method, the currentyield expression
always evaluates toNone
. The execution then continues to the next yield expression, where the generator is suspended again, and the value of theexpression_list
is returned to__next__()
’s caller. If the generator exits without yielding another value, aStopIteration
exception is raised.generator.send(value)
Resumes the execution and “send
s” a value into the generator function. The value argument becomes the result of the currentyield expression
. Thesend()
method returns the next value yielded by the generator, orraises StopIteration
if the generator exits without yielding another value. Whensend()
is called to start the generator, it must be called withNone
as the argument, because there is noyield expression
that could receive the value.generator.throw(type[, value[, traceback]])
Raises an exception of typetype
at the point where the generator was paused, and returns the next valueyield
ed by the generator function. If the generator exits without yielding another value, aStopIteration
exception is raised. If the generator function does not catch the passed-in exception, or raises a different exception, then that exception propagates to the caller.generator.close()
Raises aGeneratorExit
at the point where the generator function was paused. If the generator function then exits gracefully, is already closed, or raisesGeneratorExit
(by not catching the exception), close returns to its caller. If the generator yields a value, a RuntimeError is raised. If the generator raises any other exception, it is propagated to the caller.close()
does nothing if the generator has already exited due to an exception or normal exit.
g.close()
is defined by the following pseudo-code:
def close(self):
try:
self.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
pass
else:
raise RuntimeError("generator ignored GeneratorExit")
# Other exceptions are not caught
Yield
- return v.s. StopIteration
When a return
statement is encountered, control proceeds as in any function return, executing the appropriate finally clauses (if any exist). Then a StopIteration
exception is raised, signalling that the iterator is exhausted. A StopIteration
exception is also raised if control flows off the end of the generator without an explicit return.
Note that return
means "I'm done, and have nothing interesting to return", for both generator functions and non-generator functions.
Note that return
isn't always equivalent to raising StopIteration
: the difference lies in how enclosing try/except constructs are treated. For example,:
>>> def f1():
... try:
... return
... except:
... yield 1
...
>>> list(f1())
[]
>>> def f2():
... try:
... raise StopIteration
... except:
... yield 42
...
>>> list(f2())
[42]
yield from
When yield from <expr>
is used, it treats the supplied expression as a subiterator. All values produced by that subiterator are passed directly to the caller of the current generator’s methods. Any values passed in with send()
and any exceptions passed in with throw()
are passed to the underlying iterator if it has the appropriate methods.
When the underlying iterator is complete, the value attribute of the raised StopIteration instance becomes the value of the yield expression
. It can be either set explicitly when raising StopIteration, or automatically when the sub-iterator is a generator (by returning a value from the sub-generator).
- 0
For simple iterators,
yield from iterable
is essentially just a shortened form of
for item in iterable:
yield item
- 1
RESULT = yield from EXPR
is semantically equivalent to
_i = iter(EXPR)
try:
_y = next(_i)
except StopIteration as _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except GeneratorExit as _e:
try:
_m = _i.close
except AttributeError:
pass
else:
_m()
raise _e
except BaseException as _e:
_x = sys.exc_info()
try:
_m = _i.throw
except AttributeError:
raise _e
else:
try:
_y = _m(*_x)
except StopIteration as _e:
_r = _e.value
break
else:
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
_r = _e.value
break
RESULT = _r
- 2
In a generator, the statement
return value
is semantically equivalent to
raise StopIteration(value)
- 3
The StopIteration exception behaves as though defined thusly:
class StopIteration(Exception):
def __init__(self, *args):
if len(args) > 0:
self.value = args[0]
else:
self.value = None
Exception.__init__(self, *args)
- 4
However, unlike an ordinary loop, yield from allows subgenerators to receive sent and thrown values directly from the calling scope, and return a final value to the outer generator:
>>> def accumulate():
... tally = 0
... while 1:
... next = yield
... if next is None:
... return tally
... tally += next
...
>>> def gather_tallies(tallies):
... while 1:
... tally = yield from accumulate()
... tallies.append(tally)
...
>>> tallies = []
>>> acc = gather_tallies(tallies)
>>> next(acc) # Ensure the accumulator is ready to accept values
>>> for i in range(4):
... acc.send(i)
...
>>> acc.send(None) # Finish the first tally
>>> for i in range(5):
... acc.send(i)
...
>>> acc.send(None) # Finish the second tally
>>> tallies
[6, 10]