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/Falseoutput, 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)),f2isNoneTypeandf2.__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.
You’re correct that the parentheses don’t do anything, but the
==test doesn’t show that;falways returnsNone, sof(a) == f(b)for any two valuesaandb(that allowfto return).(l for l in range(5))is an expression that creates a generator object.fprints the generator object when you call it because the generator object is an element ofargsand you told it to print every element ofargs. If you triedf(1) == f(2)it would print 1 and 2.Yes.
(l for l in range(5))evaluates to a generator expressionf(*value)iterates overvalueand converts it to an argument list, callingfwith that argument listargs == (1, 2, 3, 4). You canprint(args)to see this; it’s just a tuple.Sure.