sardok
sardok

Reputation: 1116

lazy evaluation in generator

consider following function;

def myfunc():
    a=b=c=0
    x='12'
    a,b,c=(i for i in x)
    return a,b,c

This function throws an exception of following: ValueError: need more than 2 values to unpack. My intention here is to assign available values in the 'x' variable, to the variables on left hand side in given order. Thus a=1, b=2, c=3, what i would like to do.

In order to improve my understanding about generators, i disassembled the function

>>> dis.dis(myfunc)
  2           0 LOAD_CONST               1 (0)
              3 DUP_TOP             
              4 STORE_FAST               0 (a)
              7 DUP_TOP             
              8 STORE_FAST               1 (b)
             11 STORE_FAST               2 (c)

  3          14 LOAD_CONST               2 ('12')
             17 STORE_FAST               3 (x)

  4          20 LOAD_CONST               3 (<code object <genexpr> at 0x297b430, file "<stdin>", line 4>)
             23 MAKE_FUNCTION            0
             26 LOAD_FAST                3 (x)
             29 GET_ITER            
             30 CALL_FUNCTION            1
             33 UNPACK_SEQUENCE          3
             36 STORE_FAST               0 (a)
             39 STORE_FAST               1 (b)
             42 STORE_FAST               2 (c)

  5          45 LOAD_FAST                0 (a)
             48 LOAD_FAST                1 (b)
             51 LOAD_FAST                2 (c)
             54 BUILD_TUPLE              3
             57 RETURN_VALUE   

What i guess is UNPACK_SEQUENCE is throwing an exception. Is it possible to do STORE_FAST before UNPACK_SEQUENCE? Hoping that my question makes sense.

Upvotes: 0

Views: 508

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1124758

You are assigning to three different variables using unpacking. Unpacking has to iterate over the right-hand sequence in order to do so.

The UNPACK_SEQUENCE is indeed throwing the exception, because your generator expression only yields two values, the strings '1' and '2', but your left-hand side names list has 3 names.

This is not a problem with generators. This is a restriction for tuple assignments, explicitly documented:

  • If the target list is a comma-separated list of targets: The object must be an iterable with the same number of items as there are targets in the target list, and the items are assigned, from left to right, to the corresponding targets.

The same error would be thrown when using a list:

a, b, c = list(x)

throws the same exception, unless x is length 3, e.g. x = '123'

You are free to assign the generator expression to one variable:

>>> x = '12'
>>> a = (i for i in x)
>>> a
<generator object <genexpr> at 0x105046dc0>

If you expected c to remain set to 0 because the generator doesn't provide enough values, then that is not how tuple assignment works.

You could use a helper function with defaults for that:

def helper(a=0, b=0, c=0):
    return a, b, c

a, b, c = helper(*(i for i in x))

Demo:

>>> def helper(a=0, b=0, c=0):
...     return a, b, c
... 
>>> a, b, c = helper(*(i for i in x))
>>> a, b, c
('1', '2', 0)

The helper function uses default values for all parameters, and the * call syntax uses the generator to treat these as positional parameters.

Upvotes: 2

Related Questions