12

In python, when I assign a list to another, like:

a = [1,2,3]
b = a

Now b and a point to the same list. Now considering two lists,

a = [1,2,3]
b = [4,5,6]
a,b = b,a

Now how is it that they are swapped like any other data type and does not end up both pointing to the same list?

thefourtheye
  • 233,700
  • 52
  • 457
  • 497
Aswin Murugesh
  • 10,831
  • 10
  • 40
  • 69

3 Answers3

12

Looks like Python internally swaps the items. Check this program

a, b = [1, 2], [2, 3]

def func():
    a, b = b, a

import dis
dis.dis(func)

Output

  4           0 LOAD_FAST                0 (b)
              3 LOAD_FAST                1 (a)
              6 ROT_TWO             
              7 STORE_FAST               1 (a)
             10 STORE_FAST               0 (b)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

So, Python pushes references from b and a in the stack with LOAD_FAST. So, now the top most element is the reference pointed by a and the next one is the reference pointed by b. Then it uses ROT_TWO to swap the top two elements of the stack. So, now, the top most element is the reference pointed by b and the next one is the reference pointed by a and then assigns the top two elements of the stack to a and b respectively with STORE_FAST.

That's how sorting is happening in the assignment statement, when the number of items we deal with is lesser than 4.

If the number of items is greater than or equal to four, it builds a tuple and unpacks the values. Check this program

a, b, c, d = [1, 2], [2, 3], [4, 5], [5, 6]

def func():
    a, b, c, d  = d, c, b, a

import dis
dis.dis(func)

Output

  4           0 LOAD_FAST                0 (d)
              3 LOAD_FAST                1 (c)
              6 LOAD_FAST                2 (b)
              9 LOAD_FAST                3 (a)
             12 BUILD_TUPLE              4
             15 UNPACK_SEQUENCE          4
             18 STORE_FAST               3 (a)
             21 STORE_FAST               2 (b)
             24 STORE_FAST               1 (c)
             27 STORE_FAST               0 (d)
             30 LOAD_CONST               0 (None)
             33 RETURN_VALUE
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • @MartijnPieters That's why I had to make `references` in bold. – thefourtheye Dec 14 '13 at 12:51
  • @MartijnPieters Got you :) Please check my updated answer, including the tuple unpacking stuff. – thefourtheye Dec 14 '13 at 12:56
  • 1
    As for the optimisation I talked about; it is indeed limited to two and three item unpacks; see [`peephole.c`](http://hg.python.org/cpython/file/31fd129960d5/Python/peephole.c#l426) if you are interested. For two items, `BUILD_TUPLE` and `UNPACK_SEQUENCE` is replaced by `ROT_TWO`, for three items it is replaced by `ROT_THREE` then `ROT_TWO`. Because 4 items would require *three* opcodes (`ROT_FOUR`, then `ROT_THREE` then `ROT_TWO`) it is no longer worth optimizing for. – Martijn Pieters Dec 14 '13 at 13:01
  • @MartijnPieters Good that you read this. :) Otherwise this answer would have been misleading. Updated that sentence. Please check now – thefourtheye Dec 14 '13 at 13:02
2

Because Python assignment first evaluates the right-hand-side expression, then applies the result to the left-hand-side targets.

So, first, Python creates (<reference to list b>, <reference to list a>) as a tuple, then assigns the first item in that tuple to a, and the second item in that tuple to b. This swaps the references around neatly.

You could expand the assignment to read it like this:

tmp = b, a
a = tmp[0]
b = tmp[1]
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Looks like Python doesn't create a tuple here. Please check my answer. – thefourtheye Dec 14 '13 at 12:21
  • No, because the assignment is optimised. – Martijn Pieters Dec 14 '13 at 12:34
  • That comment went out a little too early (on my mobile). The assignment is optimised; the compiler skips creating a tuple when multiple assignment is used. There is no point. But for *understanding what happens* imagining it is a tuple is fine. In the expanded assignment example `tmp` *is* a tuple. – Martijn Pieters Dec 14 '13 at 12:47
  • Yup. That was my initial understanding. Even my first edit had that tuple based answer only. I simply tried `dis` and got surprised. :) – thefourtheye Dec 14 '13 at 12:50
  • I think it's pretty much optional whether you think of the LHS of the assignment as being a tuple, or as being a grammar production consisting of a comma-separated list of entities (which themselves may be such lists). Of course it's both, but in general when a comma-separated list of sub-expressions is evaluated for its result a tuple is created. When that same grammar production (called `testlist` in the formal Python grammar) is assigned to, one isn't. – Steve Jessop Dec 14 '13 at 13:23
  • 1
    @SteveJessop: the compiler produces a `BUILD_TUPLE` opcode in both cases, but the peephole optimizer then replaces that opcode with `ROT_` opcodes when there is an assignment on the LHS and only 3 or 2 values are involved. For more elements, tuples are still actually created. – Martijn Pieters Dec 14 '13 at 13:28
  • @MartijnPieters: ah, thanks. I didn't realise this was a special case. @Aswin: I think it's also instructive to work through why `a[:], b[:] = b, a` doesn't swap the contents of the lists -- once it's obvious to you why not, and also obvious why `a, b = b, a` does swap the references, then you probably understand how the unpacking and assignment works :-) – Steve Jessop Dec 14 '13 at 13:29
0

Now how is it that they are swapped like any other data type and does not end up both pointing to the same list?

Because the moment you unpack the tuple b, a into the tuple a, b you loose references to the original a and b and they get re-assigned.

James Mills
  • 18,669
  • 3
  • 49
  • 62