0

If I were to initialize two variables and swap them 'pythonically', elements get swapped.

a, b = 1, 0
a, b = b, a

>>> a
0
>>> b
1

But if I try to use the same pythonic syntax for following code, array remains unchanged.

>>> arr = [1, 0]
>>> arr[0], arr[arr[0]] = arr[arr[0]], arr[0]
>>> arr
[1, 0]

Why does this not swap the elements of the array?

I went through this question but it is still not clear to me how LOAD_FAST & STORE_FAST work for the second example (swapping elements of an array using indices).

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Mr Matrix
  • 1,128
  • 2
  • 14
  • 28
  • 3
    Why are you trying to use double indexing when you could do `arr[0], arr[1] = arr[1], arr[0]`? – wjandrea Sep 13 '20 at 00:18
  • You shouldn't modify an array and use the modified value in a "pythonically" joined statement like this. From my tests, if the first assignment executes first, the result is [0,0]. If the second executes first, it's [1,1]. But combined, it's [1,0]. You're better off splitting up the swap into multiple lines. – luthervespers Sep 13 '20 at 00:27
  • `arr = [1, 0] arr = [arr[1], arr[0]] #arr => [0,1]` – Anup Tiwari Sep 13 '20 at 00:29
  • 2
    Assignment takes place from left to right. First `arr[0]` is assigned to, so the second assignment to `arr[arr[0]]` will use the *new* value of `arr[0]`. – Martijn Pieters Sep 13 '20 at 01:01
  • @wjandrea - this is just a simple example extracted from another algorithm I am working on, I obviously wasn't trying to swap elements of an array of size 2. – Mr Matrix Sep 13 '20 at 01:31

2 Answers2

2

Your problem becomes even more acute if you consider the simplified assignment:

arr[0], arr[arr[0]] = 0, 1

There are actually two assignments statements here, executed sequentially:

arr[0] = 0      # arr: [0,0]
arr[arr[0]] = 1 # arr: [1,0], because arr[0] was 0

The first item of arr is changed twice, but the second is not changed at all.

DYZ
  • 55,249
  • 10
  • 64
  • 93
0

To determine when __get__ and __set__ is called, try this class:

from collections.abc import MutableSequence, Iterable


class PrintingMutable(MutableSequence):
    def __init__(self, source: (MutableSequence, Iterable) = None):
        try:
            self.arr = list(source)
        except TypeError:
            self.arr = []

    def __repr__(self):
        return repr(self.arr)

    def insert(self, index: int, o) -> None:
        self.arr.insert(index, o)  # fail-fast
        self.on_insert(index, o)

    def __getitem__(self, i: (int, slice)):
        val = self.arr[i]  # creating unnecessary name to fail-fast.

        if isinstance(i, slice):
            for idx in range(i.start, i.stop, i.step if i.step else 1):
                self.on_get(idx)
        else:
            self.on_get(i)

        return val

    def __setitem__(self, i: int, o) -> None:
        self.arr[i] = o
        self.on_set(i)

    def __delitem__(self, i: int) -> None:  # I don't think I'm gonna use it.
        del self.arr[i]
        self.on_del(i)

    def __len__(self) -> int:
        return len(self.arr)

    def on_insert(self, idx, item):
        print(f"insert item {item} at {idx}")
        self.print_statistics()

    def on_get(self, idx):
        print(f"get item {self.arr[idx]} at {idx}")
        self.print_statistics()

    def on_set(self, idx):
        print(f"set item {self.arr[idx]} at {idx}")
        self.print_statistics()

    def on_del(self, idx):
        print(f"del item {self.arr[idx]} at {idx}")
        self.print_statistics()

    def print_statistics(self):
        print(self)

And play around in Python console - you'll see the sequence python evaluate your question. Added comments to clarify steps.

a[0], a[1] = a[1], a[0]
get item 0 at 1
[1, 0]
get item 1 at 0
[1, 0]
set item 0 at 0
[0, 0]
set item 1 at 1
[0, 1]

Normally you'd expect __getitem__ and __setitem__ get/assign value in same order. For this case - get 0, get 1, set 0, set 1.

For your code:

>>> from _63866011 import PrintingMutable
>>> a = PrintingMutable([1, 0])
>>> a[0], a[a[0]] = a[a[0]], a[0]
get item 1 at 0  # get a[0] = 1
[1, 0]
get item 0 at 1  # get a[a[0]] = 0
[1, 0]

get item 1 at 0
[1, 0]
set item 0 at 0
[0, 0]

get item 0 at 0
[0, 0]
set item 1 at 0
[1, 0]

get 1, get 2, set 1, set 2, at same order just like above.

But use of list item to access list is messing up with indices.

Keep playing with this until you've got idea how assignment works in MutableSequence.

jupiterbjy
  • 2,882
  • 1
  • 10
  • 28