The other day, I shadowed an interview with a data science candidate. The primary focus is obviously not on coding skills, but we do want to assess basic knowledge of the programming language of his choice. So, my colleague asked a very simple python question to warm him up. The question is: ‘how do you swap values of two variables wtihout using a temprary variable?’. To my surprise the candidate had no clue it is as simple as a, b = b, a.

In most other languages this is not a valid statement. The reason it works in python is as follows:

  1. the expression on the right hand side gets evaluated. As a result, a tuple of two elements (a, b) is created.
  2. then python unpack this tuple, assign the values to each variables on the left hand side sequentially in the left to right order.

So if a = 5; b = 3. When python evaluates a, b = b, a:

  1. It first create a tuple of (3, 5)
  2. Then it assigns 3 to a
  3. Finally it assigns 5 to b

This is very handy and readable. However, users may think, as long as they aligned elements on the left with the corresponding elements on the right, the swap should always work. Indeed, if there is no error, code will be excuted, but not always as intened. The sequential order of unpacking should be considered when we write multiple assignments via tuple unpacking to avoid any ‘suprising’ behavior.

Let’s say that we want to do a simple linked list reversal. In other programming language, we will usually use a temprary variable to hold the next node from curr to make sure we can advance to it after we rewire curr.next to prev:

def reverse_linked_list(head):
    prev, curr = None, head
    while curr:
        next_ = curr.next
        curr.next = prev
        prev = curr
        curr = next_
    return prev

With a quick drawing, you can picture how this rewiring works and verify it is correct.

With tuple unpacking, we can do the same thing in python like this:

while curr: curr.next, prev, curr = prev, curr, curr.next

This follows the assignments order from above with temprary variable swap pattern.

However this will also work.

while curr: prev, curr.next, curr = curr, prev, curr.next

At first read, one may feel the first two elements on both sides are out of order. But it does not matter, because the reference to the node objects are already stored in the tuple before the unpacking. However, this does not mean any order will work:

while curr: curr, prev, curr.next = curr.next, curr, prev
while curr: prev, curr, curr.next = curr, curr.next, prev

Both above will not work. Because the sequential unpacking. In the last two versions, after the first two unpacking, curr will in both cases become referencing to the second node in the linked list. And the last unpacking assignment will wirte this node’s next pointer to what prev was at the begining of this unpacking happened, which is None. As a result, the loop will throw error in the second iteration, as we will try to access .next from None.

If the above is hard to wrap your head around, the one-liner is roughly equivlent to:

while curr:
    snapshot = (prev, curr, curr.next)
    curr.next = snapshot[0]
    prev = snapshot[1]
    curr = snapshot[2]