3

I have a list called ones that changes value after a block of code that shouldn't affect it. Why?

s = 3

ones = []
terms = []
for i in range (0, s):
    ones.append(1)
terms.append(ones)
print(terms)

twos = []
if len(ones) > 1:
    twos.append(ones)
    twos[-1].pop()
    twos[-1][-1] = 2
    print(twos)

print(terms)

Output:

[[1, 1, 1]]  # terms
[[1, 1, 2]]  # twos
[1, 1, 2]    # terms

For context, I'm trying to use this to begin to solve the problem on page 5 of this British Informatics Olympiad past exam: http://www.olympiad.org.uk/papers/2009/bio/bio09-exam.pdf.

martineau
  • 119,623
  • 25
  • 170
  • 301
HelloWorld4444
  • 125
  • 1
  • 9
  • Both `terms.append(ones)` and `twos.append(ones)` append references to the list held in `ones`, `twos[-1][-1] = 2` mutates that list. – Ilja Everilä Jan 02 '18 at 13:46
  • 1
    Related: https://stackoverflow.com/questions/240178/list-of-lists-changes-reflected-across-sublists-unexpectedly, https://stackoverflow.com/questions/2612802/how-to-clone-or-copy-a-list – Ilja Everilä Jan 02 '18 at 13:49
  • @khelwood Thank you for the reply. `terms` contains `ones` once, as far as I can see. What am I missing? – HelloWorld4444 Jan 02 '18 at 13:49
  • @IljaEverilä I'm sorry, but I'm not quite sure what you mean by references. I thought that `twos.append(ones)` simply added the content of `ones` onto the end of `twos`. Is that wrong? – HelloWorld4444 Jan 02 '18 at 13:50
  • 2
    It is wrong. For that you'd use [`list.extend()`](https://docs.python.org/3/tutorial/datastructures.html). – Ilja Everilä Jan 02 '18 at 13:50
  • 1
    It seems, you use Python 2. I recommend to use Python 3, if you just started. – Mr. T Jan 02 '18 at 14:05
  • @Piinthesky Good shout! I'm downloading it now. Apart from print, xrange and integer division, are there any key differences I should know about (every article I can see looks very long!)? – HelloWorld4444 Jan 02 '18 at 14:12

3 Answers3

3

twos.append(ones) does not copy ones.

There is only ever one list ones in memory, which also goes by the following references:

  1. terms[0]
  2. twos[0]

and also terms[-1] and twos[-1] because terms and twos only have one element each, so the first is the last.

Now, when you mutate ones/terms[0]/terms[-1]/twos[0]/twos[-1] you are mutating the same list in memory.

I highly recommend watching Facts and Myths about Python names and values.

timgeb
  • 76,762
  • 20
  • 123
  • 145
3

Here:

twos.append(ones)

You are appending a reference to ones, not its values. See the difference:

In [1]: l1 = [1, 2, 3]

In [2]: l2 = []

In [3]: l2.append(l1)

In [4]: l2, l1
Out[4]: ([[1, 2, 3]], [1, 2, 3])

In [5]: l2[0][1] = 'test'

In [6]: l2, l1
Out[6]: ([[1, 'test', 3]], [1, 'test', 3])

In order to avoid this you can give a copy by using [:] operator:

In [7]: l1 = [1, 2, 3]

In [8]: l2 = []

In [9]: l2.append(l1[:])

In [10]: l2, l1
Out[10]: ([[1, 2, 3]], [1, 2, 3])

In [11]: l2[0][1] = 'test'

In [12]: l2, l1
Out[12]: ([[1, 'test', 3]], [1, 2, 3])
gonczor
  • 3,994
  • 1
  • 21
  • 46
1

When you do twos.append(ones), you're passing the reference to the ones list, not the value itself. Therefore, when you do twos[-1][-1] = 2, it'll modify the value in the ones list itself, not a copy in the twos list.

To pass the value instead of the reference to the ones list, you can do:

twos.append(ones[:])
MLavrentyev
  • 1,827
  • 2
  • 24
  • 32