What's the difference between the following codes?
def bottom():
yield 42
def bottom():
return (yield 42)
What's the difference between the following codes?
def bottom():
yield 42
def bottom():
return (yield 42)
The first case returns a generator, which is an object that contains an enumerator, has a state where this enumerator is. Anyone calling the function can get the result from the enumerator. This is transparent and will give 42.
In the second case, the yield
returns the enumerator of its expression right there and then the result of that enumerator is returned from the function, it is already 42.
In practice nothing changes, in this case, but the internal mechanism is quite different.
You can view more at: What is Yield for? .
In short, this is the mechanism used for co-routines to return values in Python - details how it works as follows:
First, if the body of a function has the keyword yield
, yield from
or is declared as async def
, return
does not work as a normal return. What is returned when calling such a function as a normal function is a co-routine or a generator:
>>> def a():
... return (yield 42)
...
>>> a()
<generator object a at 0x7fc904b167d8>
The value of the result - which is in return
- of a generator or co-routine appears as the .value
attribute of the StopIteration
Exception caused by the iterator.
That is, this code:
def a():
return (yield 42)
try:
b.send("final value")
except StopIteration as end:
end2 = end
print(end2.value)
It will print "initial value". Note that this mechanism will rarely be used directly by the programmer, and is used internally by the asynchronous code support code.
The send
method was also introduced in Python 2, a version after the creation of the keyword yield
- I believe in Python 2.5 - and allows values to be sent back into the generator (as the final value of the expression yield
). From this mechanism, plus the .throw
method that allows you to cause an exception where yield
is, allowed the generators to start working as co-routines.
More direct use happens when the generator is used from within another generator, with the form yield from
. In this case, the value returned as a result of yield from
is the value of return
of the innermost generator. That is, the code below prints "None" (which is the result of yield
in the previous function when it is only used with next
:
def c():
value = yield from a()
print(value)
return None
[_ for _ in c()]
If you are viewing code that does not use asynchronous programming, and has return
values within function bodies with yield
, those generators are probably being used that way.
yield
within them starts to serve not to generate a significant value, as in the case of common generators, but to be a break point of these functions. Co-routines are always called, unlike generators, by a more complex code responsible for coordinating their call, and calling other co-routines - this is the mechanism used in asyncio
- the official library for asynchronous code execution in Python. This coordinating code is the event loop of the program that works asynchronously.
In this case, the code that uses the generator is not called directly - it is called from within other co-routines with the keyword await
, or is scheduled in the event loop , from so that it returns a result when it is finished. (Event loop) does all the coordination to catch the .value
of StopIteration, or throw an exception in the co-routine correctly, and call other co-routines when the code is "paused" by a "yield from" or "await")
In Python 3.4, the use of the asyncio.coroutine
fault decorator marked a generator using yield from
to function as a co-routine. So if you have asynchronous code made to work with Python 3.4 (or emulate this with Python 2.7), it will make use of yield from
to call other co-routines in your body, and use return
to return the final result . A specialized syntax has been created from Python 3.5 - as described above. The co-routines are declared with async def
instead of needing the coroutine
decorator, and the await
keyword is used in place of yield from
. (In short, in other named co-routines, the value in the "return" is the "normally" returned for a function called in valor = await funcao()
form.
I recently wrote another extensive answer where I talk about some more aspects of asynchronous programming - take a look there: Python asynchronous generators