Let's say I have some function f
that will accept a variable number of arguments:
def f(*args):
for a in args:
print(a)
Below I call on this function three ways:
f(l for l in range(5))
>>> <generator object <genexpr> at 0x1234>
f((l for l in range(5)))
>>> <generator object <genexpr> at 0x1234>
*
aka splat:f(*(l for l in range(5)))
>>> 0
>>> 1
>>> 2
>>> 3
>>> 4
f(l for l in range(5)) == f((l for l in range(5)))
returns:
<generator object <genexpr> at 0x7f8b2a5df570>
<generator object <genexpr> at 0x7f8b2a5df570>
True
This indicates to me that the parenthesis around the generator expression don't actually do anything. Is that correct?True
/False
output, it couldn't help itself and showed me that it created a generator object for each of the function calls(?). That said, if I assign the output to a new variable, i.e. f2 = f(l for l in range(5))
, f2
is NoneType
and f2.__next__()
throws an error (TypeError: 'NoneType' object is not an iterator
), indicating to me that the generator I just created can't be assigned to a variable.*
expanded? Put another way, are the parenthesis only there because *l for l in range(5)
can't be understood by the interpreter?f()
"see"?Should it help, I'm reading this Real Python tutorial on asyncio
, in which they repeatedly use generator expansions(?) like:
async def main():
res = await asyncio.gather(*(makerandom(i, 10 - i - 1) for i in range(3)))
return res
...which I gather is equal to:
async def main():
res = await asyncio.gather(makerandom(0, 9),makerandom(1, 8), makerandom(2, 7))
return res
...but this is the first time in my Python career that I've really confronted The Generator Arts.
This indicates to me that the parenthesis around the generator expression don't actually do anything. Is that correct?
You’re correct that the parentheses don’t do anything, but the ==
test doesn’t show that; f
always returns None
, so f(a) == f(b)
for any two values a
and b
(that allow f
to return).
Further, when tested for equality in question 2 above, while I would have expected a simple True/False output, it couldn't help itself and showed me that it created a generator object for each of the function calls(?).
(l for l in range(5))
is an expression that creates a generator object. f
prints the generator object when you call it because the generator object is an element of args
and you told it to print every element of args
. If you tried f(1) == f(2)
it would print 1 and 2.
Are the parenthesis in case 3 simply there to package up my expression so that it can be
*
expanded? Put another way, are the parenthesis only there because*l for l in range(5)
can't be understood by the interpreter?
Yes.
In case 3, what is the order of operations?
(l for l in range(5))
evaluates to a generator expressionf(*value)
iterates over value
and converts it to an argument list, calling f
with that argument listContinuing from the previous question - in case 3, what does
f()
"see"?
args == (1, 2, 3, 4)
. You can print(args)
to see this; it’s just a tuple.
What is the correct verbiage to describe case 3? "Expanding a generator and passing it into a function"?
Sure.