Search code examples
pythonpython-3.xswapiterable-unpacking

Why is my code to swap two elements of a list going wrong?


Here is my code:

a = [1, 2, 3, 4, 5]
a[0], a[a[0]] = a[a[0]], a[0]
print(a)

I'm trying to swap a[0] with a[a[0]] (i.e. a[1] in this case), so the result I expect is:

[2, 1, 3, 4, 5]

The result I get is [2, 2, 1, 4, 5], which is not what I want.

if I simplify a[0], a[a[0]] = a[a[0]], a[0] to a[0], a[1] = a[1], a[0], it works.

How can I make this swap inside a list work like a, b = b, a does?


Solution

  • That assignment's doing quite a lot. Let's break everything down …

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

    Ok, that's the easy bit. Next:

    a[0], a[a[0]] = a[a[0]], a[0]
    

    The first thing that happens in any assignment is that the right hand side is evaluated, so:

    a[a[0]], a[0] reduces to a[1], a[0], which evaluates to (2, 1).

    Then, each assignment target in turn gets one of those items from the right hand side assigned to it:

    a[0] = 2   # 2 == first item in the (already evaluated) right hand side
    

    Now that's done, a looks like this:

    [2, 2, 3, 4, 5]
    

    Now we'll do the second assignment:

    a[a[0]] = 1   # 1 == second item in the (already evaluated) right hand side
    

    But wait! a[0] is now 2, so this reduces to

    a[2] = 1
    

    And, lo and behold, if we look at a again, it's ended up as:

    [2, 2, 1, 4, 5]
    

    What you've discovered is that although Python claims to be able to swap two values simultaneously with e.g. a, b = b, a, that isn't really true. It almost always works in practice, but if one of the values is part of the description of the other one – in this case, a[0] is part of the description of a[a[0]] – the implementation details can trip you up.

    The way to fix this is to store the initial value of a[0] before you start reassigning things:

    a = [1, 2, 3, 4, 5]
    tmp = a[0]
    a[0], a[tmp] = a[tmp], a[0]
    

    After which, a looks the way you'd expect:

    [2, 1, 3, 4, 5]