Skip to content


Python value swap

Been looking (again) at XMPP recently. While browsing through existing source code and samples in several languages, there’s one pattern which comes back quite frequently in ‘echobot’ demos: when a message comes in, the to and from attributes are swapped, and the message is sent.

The most common approach is something like (pseudocode):

temp = from
from = to
to = temp

In Python there’s an easier approach though which seems to be unknown to several developers. It uses the multi-assignment/expansion syntax:

from, to = to, from

Basically, the tuple on the right (to, from) is constructed, then expanded to locals ‘from’ and ‘to’.

Just a hint :-) It’s a pretty elegant line of code IMHO.

Posted in Development.

Tagged with .


8 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Kristof Provost says

    In case anyone cares Perl has a very similar construct:
    ($a, $b) = ($b, $a);

  2. Marius Gedminas says

    Python rules.

    In a rather similar way, Python lets you compare more than two values at once, e.g. 1 < x < 100. Most other programming languages would require you to split that into multiple comparisons combined with boolean operators.

  3. Nicolas says

    Marius: Thanks, didn’t know that one!

  4. James Henstridge says

    If you benchmark it, you’ll find that the first version using the temporary variable is faster than the second version. This isn’t too surprising when you realise that it creates a tuple object then has to pull the values out of the tuple object and finally destroy it.

    This isn’t to say that tuple unpacking isn’t useful — if you’ve already got a tuple and you want to assign its items to variables it is a very convenient language feature.

  5. Nicolas says

    James: I think you’re wrong here (in the case of swapping 2 variables).

    In [1]: def f():
       ...:     a = 1
       ...:     b = 2
       ...:     
       ...:     tmp = a
       ...:     a = b
       ...:     b = a
       ...: 
    
    In [2]: def g():
       ...:     a = 1
       ...:     b = 2
       ...:     
       ...:     a, b = b, a
       ...: 
    
    In [3]: %timeit f()
    1000000 loops, best of 3: 568 ns per loop
    
    In [4]: %timeit g()
    1000000 loops, best of 3: 546 ns per loop
    
    In [5]: %timeit f()
    1000000 loops, best of 3: 591 ns per loop
    
    In [6]: %timeit g()
    1000000 loops, best of 3: 543 ns per loop
    
    In [7]: %timeit f()
    1000000 loops, best of 3: 588 ns per loop
    
    In [8]: %timeit g()
    1000000 loops, best of 3: 515 ns per loop

    The reason is obvious when looking at the opcodes generated by the compiler (which is an optimization of the general system using tuple pack/unpack):

    In [10]: dis.dis(f)
      2           0 LOAD_CONST               1 (1)
                  3 STORE_FAST               0 (a)
    
      3           6 LOAD_CONST               2 (2)
                  9 STORE_FAST               1 (b)
    
      5          12 LOAD_FAST                0 (a)
                 15 STORE_FAST               2 (tmp)
    
      6          18 LOAD_FAST                1 (b)
                 21 STORE_FAST               0 (a)
    
      7          24 LOAD_FAST                0 (a)
                 27 STORE_FAST               1 (b)
                 30 LOAD_CONST               0 (None)
                 33 RETURN_VALUE

    In [11]: dis.dis(g)
      2           0 LOAD_CONST               1 (1)
                  3 STORE_FAST               0 (a)
    
      3           6 LOAD_CONST               2 (2)
                  9 STORE_FAST               1 (b)
    
      5          12 LOAD_FAST                1 (b)
                 15 LOAD_FAST                0 (a)
                 18 ROT_TWO             
                 19 STORE_FAST               0 (a)
                 22 STORE_FAST               1 (b)
                 25 LOAD_CONST               0 (None)
                 28 RETURN_VALUE

    The unoptimized case:

    In [12]: def h():
       ....:     a, b, c, d = d, c, b, a
       ....: 
    
    In [13]: dis.dis(h)
      2           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

  6. James Henstridge says

    I guess I was wrong for modern versions of Python. It’s good to see that the cleanest looking solution is now the most efficient (and seems to have been since at least 2.4).

  7. Chris says

    …: tmp = a
    …: a = b
    …: b = a

    Not that it matters, but the result of this is a = b, and b = b

  8. Nicolas says

    Hmh, rather stupid typo ;-) Thanks for noticing.



Some HTML is OK

or, reply to this post via trackback.