ndrizza
ndrizza

Reputation: 3415

When does a tuple assignment behave like a "swap" of the involved vars, and when does it behave like "individual sequential assignments"?

Below, I illustrate with three examples what I mean with "swapping" and "sequential execution of assignment statements".


Example 1 (Swapping)

Tuple assignment can be very handy in order to swap the contents of variables.

The following example shows how we can swap the contents of two elements in an array in a clear an concise way without the need of temporary variables:

a = [1,2]

# prints [1, 2]
print(a)

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

# prints: [2,1] and not: [2, 2] as with a sequential assignment!
print(a)

The example shows us how the tuple assignment behaves like a swap, instead of a sequential execution of doing the first assignment, and then doing the third assignment.


Example 2 (Swapping)

Here's another example that swaps three integers:

x, y, z = 1, 2, 3

# prints: 1 2 3
print(x, y, z)

# swap contents in variables:
x, y, z = z, y, x

# prints: 3 2 1 and not: 3 2 3 as with a sequential assignment!
print(x, y, z)

Example 3 (Sequential Assigment Statements)

However, once things get more complicated than simple datatypes, the tuple assignment may also behave like a sequential assignment.

Let's consider the following linked list implementation:

class ListNode:

    def __init__(self, data=None, next=None):
        self.data = data
        self.next = next

    def __repr__(self):
        is_first = True
        current = self
        l = []
        safety_count = 0
        while current and safety_count < 10:
            if not is_first:
                l.append(' -> ')
            is_first = False
            l.append(str(current.data))
            current = current.next
            safety_count += 1
        return ''.join(l)

    def __str__(self):
        return self.__repr__()

This function reverses the order of the linked list (and works perfectly fine):

def reverse_list_working(L):
    if not L:
        return L
    pre_current = None
    current = L
    while True:
        tmp_next = current.next
        current.next = pre_current
        pre_current = current
        current = tmp_next
        if not current:
            break
    return pre_current

Now, we might be tempted to get rid of the tmp_ variable through a tuple assignment, to have a swap of the variable's contents:

def reverse_list_not_working(L):
    pre_current = None
    current = L
    while True:
        pre_current, current, current.next = current, current.next, pre_current
        if not current:
            break
    return pre_current

However, this implementations gives an error, once we get to the last element. The problem here is that the tuple assignment behaves like a sequential assignment.

  1. assign: pre_current -> current
  2. assign: current -> current.next (which is None at the end of the list)
  3. assign: current.next -> pre_current : yields error, because current is None!

Upvotes: 4

Views: 553

Answers (2)

ndrizza
ndrizza

Reputation: 3415

Here's another answer that summarizes in pseudo-code how the tuple assignment works:

The following tuple-assignment:

a, b, c = e1, e2, e3

Is translated into:

e1_ = eval(e1)
e2_ = eval(e2)
e3_ = eval(e3)

a = e1_
b = e2_
c = e3_

So, note that the evaluation of expression e1 might have effects on the evaluation of the expression e2 if e1 changes some shared state that e2 accesses.

Similarly, an assignment to a might affect the assignment of b, if b is dependant on a (e.g., a = c, b = c.next).

Hence, the tuple-assignment is not just a "swap".


Example:

class Num:

def __init__(self, val):
    self.val = val

def __add__(self, other):
    return Num(self.val + other)

def __str__(self):
    return str(self.val)

class Counter:

def __init__(self):
    self.val = Num(1)

def get_and_increment(self):
    return_val = self.val
    self.val += 1
    return return_val

c = Counter()

a = Num(1) a.val = Num(2) b = a

a.val, b.val = c.get_and_increment(), c.get_and_increment()

print(str(a)) # -> prints 2 print(str(b)) # -> prints 2

Upvotes: 1

Ondrej K.
Ondrej K.

Reputation: 9679

If you take the simple example (three integers) and look at the byte code, you see:

...
LOAD_NAME                2 (z)
LOAD_NAME                1 (y)
LOAD_NAME                0 (x)
ROT_THREE
ROT_TWO
STORE_NAME               0 (x)
STORE_NAME               1 (y)
STORE_NAME               2 (z)
...

I.e. the answer is you first get values for the right hand side onto stack (simple case just get the values referred to by variable, but in complex case, that can have broader impact), then you reorder, and then you assign values from stack to corresponding variable on the left hand side.

Upvotes: 0

Related Questions