2

Can someone explain modifying vs. overwriting object reference in an easy to understand way? Here is an example of what I mean:

By modifying an object reference:

nested_list = [[]]*3
nested

result:
[[], [], []]
# now let me **modify** the object reference
nested[1].append('zzz')

result:
[['zzz'], ['zzz'], ['zzz']]

By overwriting an object reference:

nested_list = [[]]*3
nested

result:
[[], [], []]
# now let me **modify** the object reference
nested[1] = ['zzz']

result:
[[], ['zzz'], []]

Does that mean when using "append" we are only modifying the object reference while using assigning values i.e.

nested[1] = ['zzz']

we are overwriting the value and assigning nested[1] to a new object reference? Is it caused by the underlying difference between the "append" method and assigning values? If so what's the difference?

DanZimmerman
  • 1,626
  • 6
  • 23
  • 45
  • You're assigning to `nested_list`, but then printing `nested`. How are these related? – Barmar May 14 '17 at 15:50
  • http://stackoverflow.com/questions/240178/list-of-lists-changes-reflected-across-sublists-unexpectedly – timgeb May 14 '17 at 15:52
  • The `*` operator on `list`s copies the references inside it. So there are three references to the same (empty) mutable list, inside the outer list. –  May 14 '17 at 15:57

2 Answers2

2

Let's assign the name x to an empty list such that it is easier to reason about the code.

In your first example

>>> x = []
>>> nested = [x]*3
>>> nested
[[], [], []]

you are creating a list nested with three references to x. Here's proof:

>>> all(e is x for e in nested)
True

We only ever created one empty list x, that's why

nested[0].append('zzz')
nested[1].append('zzz')
nested[2].append('zzz')

and

x.append('zzz')

are all equivalent and appending to the same list in memory:

>>> nested[0].append('zzz')
>>> nested
[['zzz'], ['zzz'], ['zzz']]
>>> nested[1].append('zzz')
>>> nested
[['zzz', 'zzz'], ['zzz', 'zzz'], ['zzz', 'zzz']]
>>> nested[2].append('zzz')
>>> nested
[['zzz', 'zzz', 'zzz'], ['zzz', 'zzz', 'zzz'], ['zzz', 'zzz', 'zzz']]
>>> x.append('zzz')
>>> nested
[['zzz', 'zzz', 'zzz', 'zzz'], ['zzz', 'zzz', 'zzz', 'zzz'], ['zzz', 'zzz', 'zzz', 'zzz']]

The second example is easy. You create a list nested which initially holds three references to the same empty list.

Then you overwrite what the second element of nested (i.e. nested[1]) refers to by issuesing

>>> x = []
>>> nested = [x]*3
>>> nested[1] = ['zzz']
>>> nested
[[], ['zzz'], []]

The second element of nested is a new list that has nothing to do with the first and third element of nested.

>>> nested[0] is nested[1]
False
>>> nested[2] is nested[1]
False
>>> nested[0] is nested[2]
True

Since you did not modify what nested[0] and nested[2] reference, they are still holding the same empty list (which in our example also goes by the name x).

>>> x.append('x')
>>> nested
[['x'], ['zzz'], ['x']]
timgeb
  • 76,762
  • 20
  • 123
  • 145
  • Thanks for the response. But why does "appending" data to list change them all while assigning values overwrite one of the list instead of all? – DanZimmerman May 15 '17 at 13:46
  • @thatMeow Hmm, I thought I made that crystal clear. Maybe read the answer again? You initially have a list `nested` which holds references **to the same empty list `x` in memory**. When you mutate `x` by appending to it, of course every reference to `x` in `nested` is going to see that change. There are only ever two lists in this case. `x` and `nested`. No more. In the second example, you do not mutate `x`. You create a **new** list `['zzz']` and then say that `nested[1]` must point to that new list. `nested[0]` and `nested[2]` still point to `x`. – timgeb May 15 '17 at 13:50
  • 1
    Ah got it, putting it this way make it easier for me to understand. Thanks! – DanZimmerman May 15 '17 at 15:06
1

As I wrote in my comment, the * operator on lists just copies the references inside the list:

nested_list = [[]] * 3

All three elements inside nested_list refer to the same list. This makes sense if you think about what the expression above really says. The evaluation really happen in the following order:

nested_list = [[]]  # first create a list with an empty list.
nested_list = nested_list * 3  # duplicate the references to that empty list

To the second part of your question. If you replace the second element by a new list:

nested_list[1] = ['zzz']

The first and third element are referring to the same empty list, but the one just assigned to (['zzz']) is a new list (with one element, 'zzz'.)

E.g. if you do the following you will see that the first and third are still referring to the same list:

nested_list[0].append('a')
print(nested_list)
# [['a'], ['zzz'], ['a']]

Solution

To create three distinct empty lists, which is probably what you want, you usually do something like (the following lines are equivalent):

nested_lists = [[] for _ in range(3)]
nested_lists = [[], [], []]