How does generator * / splat unpacking work?

Question:

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:

  1. Testing for equality, 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?

  2. 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 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.
  3. 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?
  4. In case 3, what is the order of operations?
  5. Continuing from the previous question – in case 3, what does f() "see"?
  6. 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.

Asked By: Collin

||

Answers:

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 expression
  • f(*value) iterates over value and converts it to an argument list, calling f with that argument list

Continuing 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.

Answered By: Ry-
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.