Is there any difference — performance or otherwise — between generator expressions and generator functions?
In [1]: def f():
...: yield from range(4)
...:
In [2]: def g():
...: return (i for i in range(4))
...:
In [3]: f()
Out[3]: <generator object f at 0x109902550>
In [4]: list(f())
Out[4]: [0, 1, 2, 3]
In [5]: list(g())
Out[5]: [0, 1, 2, 3]
In [6]: g()
Out[6]: <generator object <genexpr> at 0x1099056e0>
I'm asking because I want to decide how I should decide between using the two. Sometimes generator functions are clearer and then the choice is clear. I am asking about those times when code clarity does not make one choice obvious.
In addition to @Bakuriu's good point — that generator functions implement
send()
,throw()
, andclose()
— there is another difference I've run into. Sometimes, you have some setup code that happens before the yield statement is reached. If that setup code can raise exceptions, then the generator-returning version might be preferable to the generator function because it will raise the exception sooner. E.g.,If one wants both behaviors, I think the following is best:
The functions you provided have completely different semantics in the general case.
The first one, with
yield from
, passes the control to the iterable. This means that calls tosend()
andthrow()
during the iteration will be handled by the iterable and not by the function you are defining.The second function only iterates over the elements of the iterable, and it will handle all the calls to
send()
andthrow()
. To see the difference check this code:In fact, due to this reason,
yield from
probably has a higher overhead than the genexp, even though it is probably irrelevant.Use
yield from
only when the above behaviour is what you want or if you are iterating over a simple iterable that is not a generator (so thatyield from
is equivalent to a loop + simpleyield
s).Stylistically speaking I'd prefer:
Instead of
return
ing a genexp or usingyield from
when dealing with generators.In fact the code used by the generator to perform the iteration is almost identical to the above function:
As you can see it does a
FOR_ITER
+YIELD_VALUE
. note that the argument (.0
), isiter(range(4))
. The bytecode of the function also contains the calls toLOAD_GLOBAL
andGET_ITER
that are required to lookuprange
and obtain its iterable. However this actions must be performed by the genexp too, just not inside its code but before calling it.