15

I found the assignment a = a[1:] = [2] in an article. I tried it in python3 and python2; it all works, but I don't understand how it works. = here is not like in C; C processes = by right to left. How does python process the = operator?

Prune
  • 76,765
  • 14
  • 60
  • 81
peter zhang
  • 1,247
  • 1
  • 10
  • 19
  • I reopened this question because it is a much more complex case than the typical `x = y = some_func()` case addressed in [How do chained assignments work?](https://stackoverflow.com/q/7601823/364696) – ShadowRanger Feb 06 '18 at 01:12
  • 2
    It can be left to right because assignment is a type of statement in Python and not an expression like in C. It doesn’t have an associativity since `a = b = … = x` is all parsed together. – Ry- Feb 06 '18 at 01:15
  • The question is only related to the non-complex part though (left-to-right). OP states they understand how it works. I am not strongly against reopening though. – ayhan Feb 06 '18 at 01:18

2 Answers2

18

Per the language docs on assignment:

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

In this case, a = a[1:] = [2] has an expression list [2], and two "target lists", a and a[1:], where a is the left-most "target list".

You can see how this behaves by looking at the disassembly:

>>> import dis
>>> dis.dis('a = a[1:] = [2]')
  1           0 LOAD_CONST               0 (2)
              2 BUILD_LIST               1
              4 DUP_TOP
              6 STORE_NAME               0 (a)
              8 LOAD_NAME                0 (a)
             10 LOAD_CONST               1 (1)
             12 LOAD_CONST               2 (None)
             14 BUILD_SLICE              2
             16 STORE_SUBSCR
             18 LOAD_CONST               2 (None)
             20 RETURN_VALUE

(The last two lines of the disassembly can be ignored, dis is making a function wrapper to disassemble the string)

The important part to note is that when you do x = y = some_val, some_val is loaded on the stack (in this case by the LOAD_CONST and BUILD_LIST), then the stack entry is duplicated and assigned, from left to right, to the targets given.

So when you do:

a = a[1:] = [2]

it makes two references to a brand new list containing 2, and the first action is a STORE one of these references to a. Next, it stores the second reference to a[1:], but since the slice assignment mutates a itself, it has to load a again, which gets the list just stored. Luckily, list is resilient against self-slice-assignment, or we'd have issues (it would be forever reading the value it just added to add to the end until we ran out of memory and crashed); as is, it behaves as a copy of [2] was assigned to replace any and all elements from index one onwards.

The end result is equivalent to if you'd done:

_ = [2]
a = _
a[1:] = _

but it avoids the use of the _ name.

To be clear, the disassembly annotated:

Make list [2]:

  1           0 LOAD_CONST               0 (2)
              2 BUILD_LIST               1

Make a copy of the reference to [2]:

              4 DUP_TOP

Perform store to a:

              6 STORE_NAME               0 (a)

Perform store to a[1:]:

              8 LOAD_NAME                0 (a)
             10 LOAD_CONST               1 (1)
             12 LOAD_CONST               2 (None)
             14 BUILD_SLICE              2
             16 STORE_SUBSCR
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
7

The way I understand such assignments is that this is equivalent to

temp = [2]
a = temp
a[1:] = temp

The resulting value of [2, 2] is consistent with this interpretation.

Prune
  • 76,765
  • 14
  • 60
  • 81