What is the logic for x,y=y,x to swap the values?

The way this mechanism works is a combination of two features -- forming implicit tuples, and tuple/list unpacking.

When you do something = x, y, what Python will do is implicitly create a tuple (a sort of immutable list) comprising of the two elements, x and y. So, the following two lines of code are exactly equivalent:

something = x, y
something = (x, y)

A tuple can, of course, contain more than two elements:

something = a, b, c, d, e
something = (a, b, c, d, e)

The intended use case of this feature is to make it easier/cleaner to do things like return multiple values from a function:

def foo():
    return "hello", "world"

The second feature is tuple/list unpacking. Whenever you have a series of variables on the left-hand side, and any sort of list, tuple, or other collection on the other, Python will attempt to match up each of the elements in the right to the ones on the left:

>>> a, b, c = [11, 22, 33]
>>> print(a)
11
>>> print(b)
22
>>> print(c)
33

If it helps, the line a, b, c = [11, 22, 33] is basically identical to doing:

temp = [11, 22, 33]
a = temp[0]
b = temp[1]
c = temp[2]

Note that the right-hand side can be literally any kind of collection, not just tuples or lists. So the following code is valid:

>>> p, q = "az"
>>> print(p + "  " + q)
a  z
>>>
>>> s, t = {'cat': 'foo', 'dog': 'bar'}
>>> print(s + "  " + t)
cat  dog

(Though, since dictionaries in Python are not obligated to be in any particular order, and since the order of the keys can be freely scrambled, unpacking them probably isn't going to be useful since you'd potentially get different results each time.)

If the number of variables and the number of elements in the collection do not match up, Python will throw an exception:

>>> a, b = (1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)

>>> a, b, c = (1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 2 values to unpack

So that means that if we called our foo function from above and did the following:

>>> x, y = foo()

...the variable x would equal the string "hello", and the variable y would equal the string "world".


So ultimately, that means that your original snippit of code:

x = "salil"
y = "Ajay"
y, x = x, y

...is logically equivalent to the following:

x = "salil"
y = "Ajay"
temp = (x, y)  # evaluates to ("salil", "Ajay")
y, x = temp

...which broken down even more, is logically equivalent to the following:

x = "salil"
y = "Ajay"
temp = (x, y)  # evaluates to ("salil", "Ajay")
y = temp[0]
x = temp[1]

Note that you can think of these two operations take place separately. First the tuple is formed and evaluated, then the tuple is unpacked back into the variables. The net effect is that the values of your two variables are interchanged.

(However, as it turns out, the CPython interpreter (the original and 'standard' implementation of Python) does a bit of optimization here: it will optimize the swap and won't do the full tuple unpacking -- see comments below. I'm not sure if other implementations of Python do the same, though I suspect they might. In any case, this sort of optimization is implementation-specific, and is independent to the design of the Python language itself.)


Okay, let's see:

import dis
src = '''
x="salil"
y="Ajay"
y,x=x,y

print x +" "+ y
'''
code = compile(src, '<string>', 'exec')
dis.dis(code)

This produces:

  2           0 LOAD_CONST               0 ('salil')
              3 STORE_NAME               0 (x)

  3           6 LOAD_CONST               1 ('Ajay')
              9 STORE_NAME               1 (y)

  4          12 LOAD_NAME                0 (x)
             15 LOAD_NAME                1 (y)
             18 ROT_TWO             
             19 STORE_NAME               1 (y)
             22 STORE_NAME               0 (x)

  6          25 LOAD_NAME                0 (x)
             28 LOAD_CONST               2 (' ')
             31 BINARY_ADD          
             32 LOAD_NAME                1 (y)
             35 BINARY_ADD          
             36 PRINT_ITEM          
             37 PRINT_NEWLINE       
             38 LOAD_CONST               3 (None)
             41 RETURN_VALUE        

Remember that Python operates as a stack machine. In this case, it optimized the assignment to a ROT_TWO (i.e. swap) instruction. There's also a ROT_THREE instruction. But let's try something else:

import dis
src = 'x, y, z, w = a, b, c, d'
code = compile(src, '<string>', 'exec')
dis.dis(code)

This produces the general form:

  1           0 LOAD_NAME                0 (a)
              3 LOAD_NAME                1 (b)
              6 LOAD_NAME                2 (c)
              9 LOAD_NAME                3 (d)
             12 BUILD_TUPLE              4
             15 UNPACK_SEQUENCE          4
             18 STORE_NAME               4 (x)
             21 STORE_NAME               5 (y)
             24 STORE_NAME               6 (z)
             27 STORE_NAME               7 (w)
             30 LOAD_CONST               0 (None)
             33 RETURN_VALUE        

This works for swapping because the right hand side of the = is evaluated first.

So on the right, it evaluates to

'salil', 'Ajay'

and then the assignment of x and y happens

 x, y = 'salil', 'Ajay'

Tags:

Python