pythongenerator

What is the Python rule about "unpacking" a generator?


Here's a simple bounded generator:

def bounded_naturals(limit):
    num = 1
    while num <= limit:
        yield num
        num += 1

If I write

bn_gen = bounded_naturals(3)

bn_gen will be a generator object as expected.

But if I write

(a, b, c) = bounded_naturals(3)

a, b, and c will be 1, 2, and 3 respectively. That strikes me as strange since there seems to be nothing in the code that asks the generator to produce values. Is there a place in the Python specification that requires this interpretation?

Even more striking, if I write

bn_gen = (a, b, c) = bounded_naturals(3)

I get both results! bn_gen will be a generator object, and a, b, and c will be 1, 2, and 3. How should I understand what's going on?

Finally, if I write

(a, b) = bounded_naturals(3)

I get: ValueError: too many values to unpack (expected 2).

If the compiler is clever enough to do these other tricks, why isn't it clever enough to ask the generator for only as many elements as needed in this case?

Is there a section in the Python documentation that explains all this?


Solution

  • Unpacking operates on arbitrary iterables, not sequences, and it does so by iteration. When you do

    (a, b, c) = bounded_naturals(3)
    

    you are asking Python to iterate over bounded_naturals(3) and assign the results to a, b, and c.


    A multiple assignment like

    bn_gen = (a, b, c) = bounded_naturals(3)
    

    works by assigning the RHS to each assignment target from left to right (not right to left like some other languages). The generator first gets assigned to bn_gen, then to (a, b, c). Note that the unpacking exhausts the generator, so iterating over bn_gen will give you nothing.


    When you do

    (a, b) = bounded_naturals(3)
    

    the failure has nothing to do with Python not being clever. Python will not silently discard extra values, as that would only hide bugs. The iterable must give exactly as many elements as the unpacking requests.

    Remember, just because some code could be given a non-error meaning, does not mean it should.


    All of this is documented in the assignment statement docs.