How Does Swapping of Members in Tuples (A,B)=(B,A) Work Internally

How does swapping of members in tuples (a,b)=(b,a) work internally?

Python separates the right-hand side expression from the left-hand side assignment. First the right-hand side is evaluated, and the result is stored on the stack, and then the left-hand side names are assigned using opcodes that take values from the stack again.

For tuple assignments with 2 or 3 items, Python just uses the stack directly:

>>> import dis
>>> def foo(a, b):
... a, b = b, a
...
>>> dis.dis(foo)
2 0 LOAD_FAST 1 (b)
3 LOAD_FAST 0 (a)
6 ROT_TWO
7 STORE_FAST 0 (a)
10 STORE_FAST 1 (b)
13 LOAD_CONST 0 (None)
16 RETURN_VALUE

After the two LOAD_FAST opcodes (which push a value from a variable onto the stack), the top of stack holds [a, b]. The ROT_TWO opcode swaps the top two positions on the stack so the stack now has [b, a] at the top. The two STORE_FAST opcodes then takes those two values and store them in the names on the left-hand side of the assignment. The first STORE_FAST pops a value of the top of the stack and puts it into a, the next pops again, storing the value in b. The rotation is needed because Python guarantees that assignments in a target list on the left-hand side are done from left to right.

For a 3-name assignment, ROT_THREE followed by ROT_TWO is executed to reverse the top three items on the stack.

For longer left-hand-side assignments, an explicit tuple is built:

>>> def bar(a, b, c, d):
... d, c, b, a = a, b, c, d
...
>>> dis.dis(bar)
2 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 LOAD_FAST 2 (c)
9 LOAD_FAST 3 (d)
12 BUILD_TUPLE 4
15 UNPACK_SEQUENCE 4
18 STORE_FAST 3 (d)
21 STORE_FAST 2 (c)
24 STORE_FAST 1 (b)
27 STORE_FAST 0 (a)
30 LOAD_CONST 0 (None)
33 RETURN_VALUE

Here the stack with [d, c, b, a] is used to build a tuple (in reverse order, BUILD_TUPLE pops from the stack again, pushing the resulting tuple onto the stack), and then UNPACK_SEQUENCE pops the tuple from the stack again, pushes all elements back from the tuple back onto the stack again for the STORE_FAST operations.

The latter may seem like a wasteful operation, but the right-hand side of an assignment may be something entirely different, a function call that produces a tuple perhaps, so the Python interpreter makes no assumptions and uses the UNPACK_SEQUENCE opcode always. It does so even for the two and three-name assignment operations, but a later (peephole) optimization step replaces a BUILD_TUPLE / UNPACK_SEQUENCE combination with 2 or 3 arguments with the above ROT_TWO and ROT_THREE opcodes for efficiency.

Why Python can achieve a, b = b, a

It's because python provides a special "tuple unpacking" syntax that many other languages don't have. I'll break apart what's going on:

The b, a on the right of the equals is making a tuple (remember, a tuple is pretty much the same as a list, except you can't modify it). The a, b on the left is a different syntax that's unpacking the tuple, i.e. it takes the first item in the tuple, and store it in the first variable listed, then it takes the second item and stored it in the second variable, and so forth.

This is what it looks like broken up in multiple steps:

a = 2
b = 3
my_tuple = a, b
print(my_tuple) # (2, 3)
b, a = my_tuple
print(a) # 3
print(b) # 2

And here's another, simpler examples of tuple unpacking, just to help wrap your mind around it:

x, y, z = (1, 2, 3)
print(x) # 1
print(y) # 2
print(z) # 3

By the way, Python's not the only one, Javascript can do it too, but with arrays:

[a, b] = [b, a];

How Python's a, b = b, a works?

This expression

start, stop = 0, start

will NOT be evaluated like this

start = 0
stop = start

but will be evaluated by pushing both the values to the stack and the top two values will be rotated (so that the values will be swapped) and the values are assigned back to start and stop. Lets consider,

a = 1
a, b = 0, a

If we look at the disassembled code

import dis
dis.dis(compile("a = 1; a, b = 0, a", "<string>", "exec"))

it would look like

  1           0 LOAD_CONST               0 (1)
3 STORE_NAME 0 (a)
6 LOAD_CONST 1 (0)
9 LOAD_NAME 0 (a)
12 ROT_TWO
13 STORE_NAME 0 (a)
16 STORE_NAME 1 (b)
19 LOAD_CONST 2 (None)
22 RETURN_VALUE

The LOAD_* operations will load the content in to the stack. If you look at

              6 LOAD_CONST               1 (0)
9 LOAD_NAME 0 (a)

the values, 0 and the value in a are pushed to the stack and then ROT_TWO, rotates the values in the top two positions of the stack. And finally

             13 STORE_NAME               0 (a)
16 STORE_NAME 1 (b)

assigns the rotated values to a and b respectively.

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.)

What is mechanism of using sliced Numpy arrays in a tuple assignment (Python)?

So, this isn't simple assignment. Name-object binding semantics apply to simple assignment, i.e. a = b.

If you do:

a[ix] = b

Then the exact behavior is deferred to the type, i.e., it is equivalent to

type(a).__setitem__(a, ix, b)

Numpy arrays are basically object-oriented wrappers over primitive, C-like arrays, implementing a "true" multidimensional array interface. In this context, the key thing to understand is that different numpy array objects can share underlying buffers. Simple slicing always creates a numpy.ndarray object that is a view over the original array.

So in this case, the b above is actually a call to nd.array.__getitem__. Which returns a view.

So, consider the simple case of Python lists. The right hand side:

(a[2:], a[:2]) 

Creates a tuple of two, independent list objects (although, shallow-copied).

When they are assigned to the sequence of assignment targets on the left-hand side, the mutation doesn't have any shared effect. There are three independent buffers for the three list objects (list objects will not create views).

On the other hand, the expression a[2:], a[:2] creates a tuple with the result of slicing the original nd.array object, controlled by nd.array.__getitem__. This creates two new array objects, but they are views over the underlying buffer from the original array. Basically, you are sharing the same underlying mutable, primitive buffer between three different array objects.

List value swapping: What's the correct order and why?

I reproduced this with [2, 10, -5].
The detailed sequence of operations is

i, v = 0, 2
1 <= 2 ? OK
2 != A[1] ? OK ... stay in loop
v, A[v-1] = 10, 2
# This assignment breaks into ...
v = 10
A[10-1] = 2
# ... and this second assignment is out of range.

If you switch the assignment order:

    A[v-1], v = 2, 10
# This assignment breaks into ...
A[2-1] = 2
v = 10

In this case, your while conditions have properly guarded a legal assignment.

Note that you are not swapping list values; v is a local variable; it is not a reference to A[i]. For instance, in the above example, you do get v=10, but this does not affect the value of A[0].



Related Topics



Leave a reply



Submit