What is the difference between list comprehensions and generator comprehensions with yield
inside? Both return a generator object (listcomp
and genexpr
respectively), but upon full evaluation the latter adds what seem to be rather superfluous None
s.
>>> list([(yield from a) for a in zip("abcde", itertools.cycle("12"))])
['a', '1', 'b', '2', 'c', '1', 'd', '2', 'e', '1']
>>> list(((yield from a) for a in zip("abcde", itertools.cycle("12"))))
['a', '1', None, 'b', '2', None, 'c', '1', None, 'd', '2', None, 'e', '1', None]
How come? What is the scientific explanation?
TLDR: A generator expression uses an implicit
yield
, which returnsNone
from theyield from
expression.There are actually two things behaving differently here. Your list comprehension is actually thrown away...
Understanding this is easiest if you transform the expressions to equivalent functions. For clarity, let's write this out:
To replicate the initial expressions, the key is to replace
<expr>
with(yield from a)
. This is a simple textual replacement:With
b = ((1,), (2,))
, we would expect the output1, 2
. Indeed, both replicate the output of their respective expression/comprehension forms.As explained elsewhere,
yield (yield from a)
should make you suspicious. However,result.append((yield from a))
should make you cringe...Let's look at the generator first. Another rewrite makes it obvious what is going on:
For this to be valid,
result
must have a value - namelyNone
. The generator does notyield
the(yield from a)
expression, but its result. You only get the content ofa
as a side effect of evaluating the expression.If you check the type of your "list comprehension", it is not
list
- it isgenerator
.<listcomp>
is just its name. Yes, that's not a Moon, that's a fully functional generator.Remember how our transformation put a
yield from
inside a function? Yepp, that is how you define a generator! Here is our function version, this time withprint
sprinkled on it:Evaluating
list(listfunc())
printsNone
,None
, and[None, None]
and returns[1, 2]
. Your actual list contains thoseNone
that sneaked into the generator as well! However, it is thrown away and the result is again just a side effect. This is what actually happens:listfunc
.list
iterates over it...yield from a
provides the values ofa
and returnsNone
None
is stored in the result listAt the end of the iteration...
return
raisesStopIteration
with a value of[None, None]
list
constructor ignores this and throws the value awayMoral of this story
Don't use
yield from
inside of comprehensions. It does not do what you think it does.The value of the
yield from
expression isNone
. The fact that your second example is a generator expression means that it is already implicitly yielding from the iterator, so it will also yield the value of theyield from
expression. See this for a more detailed answer.