The following behaviour seems rather counterintuitive to me (Python 3.4):
>>> [(yield i) for i in range(3)]
<generator object <listcomp> at 0x0245C148>
>>> list([(yield i) for i in range(3)])
[0, 1, 2]
>>> list((yield i) for i in range(3))
[0, None, 1, None, 2, None]
The intermediate values of the last line are actually not always None
, they are whatever we send
into the generator, equivalent (I guess) to the following generator:
def f():
for i in range(3):
yield (yield i)
It strikes me as funny that those three lines work at all. The Reference says that yield
is only allowed in a function definition (though I may be reading it wrong and/or it may simply have been copied from the older version). The first two lines produce a SyntaxError
in Python 2.7, but the third line doesn't.
Also, it seems odd
- that a list comprehension returns a generator and not a list
- and that the generator expression converted to a list and the corresponding list comprehension contain different values.
Could someone provide more information?
Generator expressions, and set and dict comprehensions are compiled to (generator) function objects. In Python 3, list comprehensions get the same treatment; they are all, in essence, a new nested scope.
You can see this if you try to disassemble a generator expression:
The above shows that a generator expression is compiled to a code object, loaded as a function (
MAKE_FUNCTION
creates the function object from the code object). The.co_consts[0]
reference lets us see the code object generated for the expression, and it usesYIELD_VALUE
just like a generator function would.As such, the
yield
expression works in that context, as the compiler sees these as functions-in-disguise.This is a bug;
yield
has no place in these expressions. The Python grammar before Python 3.7 allows it (which is why the code is compilable), but theyield
expression specification shows that usingyield
here should not actually work:This has been confirmed to be a bug in issue 10544. The resolution of the bug is that using
yield
andyield from
will raise aSyntaxError
in Python 3.8; in Python 3.7 it raises aDeprecationWarning
to ensure code stops using this construct. You'll see the same warning in Python 2.7.15 and up if you use the-3
command line switch enabling Python 3 compatibility warnings.The 3.7.0b1 warning looks like this; turning warnings into errors gives you a
SyntaxError
exception, like you would in 3.8:The differences between how
yield
in a list comprehension andyield
in a generator expression operate stem from the differences in how these two expressions are implemented. In Python 3 a list comprehension usesLIST_APPEND
calls to add the top of the stack to the list being built, while a generator expression instead yields that value. Adding in(yield <expr>)
just adds anotherYIELD_VALUE
opcode to either:The
YIELD_VALUE
opcode at bytecode indexes 15 and 12 respectively is extra, a cuckoo in the nest. So for the list-comprehension-turned-generator you have 1 yield producing the top of the stack each time (replacing the top of the stack with theyield
return value), and for the generator expression variant you yield the top of the stack (the integer) and then yield again, but now the stack contains the return value of theyield
and you getNone
that second time.For the list comprehension then, the intended
list
object output is still returned, but Python 3 sees this as a generator so the return value is instead attached to theStopIteration
exception as thevalue
attribute:Those
None
objects are the return values from theyield
expressions.And to reiterate this again; this same issue applies to dictionary and set comprehension in Python 2 and Python 3 as well; in Python 2 the
yield
return values are still added to the intended dictionary or set object, and the return value is 'yielded' last instead of attached to theStopIteration
exception: