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:
Case 1: Passing in a generator expression without parenthesis:
f(l for l in range(5))
>>> <generator object <genexpr> at 0x1234>
Case 2: Passing in a generator expression with parenthesis:
f((l for l in range(5)))
>>> <generator object <genexpr> at 0x1234>
Case 3: Passing in a generator expression with parenthesis and then expanding it with *
aka splat:
f(*(l for l in range(5)))
>>> 0
>>> 1
>>> 2
>>> 3
>>> 4
Here are my questions:
- Testing for equality,
f(l for l in range(5)) == f((l for l in range(5)))
returns:
This indicates to me that the parenthesis around the generator expression don't actually do anything. Is that correct?<generator object <genexpr> at 0x7f8b2a5df570> <generator object <genexpr> at 0x7f8b2a5df570> True
- What is happening in case 1 and 2? When called individually, the output indicates that it created a generator. 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(?). That said, if I assign the output to a new variable, i.e.f2 = f(l for l in range(5))
,f2
isNoneType
andf2.__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. - 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? - In case 3, what is the order of operations?
- Continuing from the previous question - in case 3, what does
f()
"see"? - What is the correct verbiage to describe case 3? "Expanding a generator and passing it into a function"? Is any of my phraseology above incorrect?
Context:
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.