6

I just thought I'd jot this down now that I've seen it - it would be nice to get a confirmation on this behavior; I did see How do I pass a variable by reference?, but I'm not sure how to interpret it in this context.

Let's say we have these two arrays/lists:

a = [1, 2, 3, 4]
b = [-1, a, -100, a[2], -1]

The interpreter initially sees them as:

>>> print(a)
[1, 2, 3, 4]
>>> print(b)
[-1, [1, 2, 3, 4], -100, 3, -1]

Now let's change a[2], and see what happens:

>>> print(a)
[1, 2, 55, 4]
>>> print(b)
[-1, [1, 2, 55, 4], -100, 3, -1]

So, wherever list b has a reference to the list a, the value has been updated - but wherever b was initialized with (a reference to?) an element from list a, it seems that Python expanded the value at initialization time, and thus stored the element by value (not by reference), so it's value obviously doesn't update.

Basically, I found a use case, where it would be convenient to be able to define e.g. b = [-1 a[2] -1], and then update a[2], and be able to count that the latest value of a[2] will be emitted when getting the value of (in this case) b[1]. Is there a way to do that in Python, without having to do b = [-1 a -1], and then reading b[1][2] (I'd like to get the value of a[2] just by using b[1])?

Community
  • 1
  • 1
sdaau
  • 36,975
  • 46
  • 198
  • 278

3 Answers3

2

a is a reference to a mutable list. So, when you say:

a[2] = 55

you are calling __setitem__ on the list which sets an item in the list. list.__setitem__ doesn't make any attempt to mutate the item that used to be stored in the second index. It simply replaces that reference with a new one.

On the other side, x = a[2] calls __getitem__ which just creates a new reference to the object stored at that index in the list.

mgilson
  • 300,191
  • 65
  • 633
  • 696
1
>>> a = [1000,2000,3000,4000]
>>> sys.getrefcount(a[2])
2
>>> b = [-1, a, -100, a[2], -1]
>>> a is b[1]   # b[1] and `a` are actually two variables pointing to the same object
True
#[1000,2000,3000,4000] can be accessed or modified by either `a` or `b[1]`
>>> sys.getrefcount(a)  
3

>>> sys.getrefcount(a[2])
3

Now there are total 3 references to the object 3000 in memory(a[2],b[-2] and shell itself), but as integers are immutable so if you change of modify a[2] it'll simply remove one reference from the object 3000, But b[-2] will still point to the same object in memory and a[2] will now point to some newly assigned object.

>>> id(a[2]),id(b[-2])
(150561920, 150561920)
>>> a[-2] = 5
>>> id(a[2]),id(b[-2])  #b still points to the same object
(148751024, 150561920)
>>> sys.getrefcount(b[-2])
2

If the item at a[2] is a mutable object, say list:

>>> a = [1000,2000, [2] , 4000]
>>> b = [-1, a, -100, a[2], -1]
>>> a[2] += [5]     # we can modify [2] from from either a[2] or b[-2]
>>> b[-2]+= [10]    # `+=` , `list.extend`, `list.append` changes the list in-place
>>> a[2] is b[-2]   #both still points to the same object as lists are mutable
True
>>> a
[1000, 2000, [2, 5, 10], 4000]
>>> b
[-1, [1000, 2000, [2, 5, 10], 4000], -100, [2, 5, 10], -1]
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
0

To solve the problem of accessing sublists by index of the outer list, you can use something like:

class RecursiveList(list):
    def getitem(self, index, recurse=True):
        if not recurse:
            return self[index]
        else:
            return list(RecursiveList.flatten(self))[index]

    @staticmethod
    def flatten(l):
        for item in l:
            if hasattr(item, "__iter__"):
               for v in RecursiveList.flatten(item):
                   yield v
            else:
               yield item

For the exact behaviour you ask for, add the following:

    def __getitem__(self, i):
        return self.getitem(i, true)

Note, this will probably break if you try to use slices.

RoadieRich
  • 6,330
  • 3
  • 35
  • 52