List.copy() bug when using list of lists (Solved)

I’m new to Python and was driven crazy by this “feature” or bug.

Run the program below and could someone please explain the behaviour.

If you read the documentation for copy() it clearly states a shadow copy of a list is created and returned. I am using 3.11.0 and IDLE and for a list of simple elements copy() works as expected.

Hoever copy() does not work as expected with a list of lists. Instead copy() returns a reference to the original list.

print("Code that demonstrates how copy() works with a simple list of elements")
eTestList = [1,2,3,4]

print("Test case: ",eTestList)
print()
print("Create and modify a copy of the list")
NeweList =  eTestList.copy()
# now change the new list and print the original and new list
NeweList[0]=0
print(" Original: ",eTestList)
print("     New : ",NeweList)
print("Worked as expected.  Nothing crazy here")
print()

print()
print("Code that demonstrates how copy() does not work with list of lists")
lolTestList = [[1,0,0,0]] 
lolTestList.append([2,0,0,0])
lolTestList.append([3,0,0,0])

print("Test case: ",lolTestList)
print()
print("Create and modify a copy of the list")
NewlolList =  lolTestList.copy()
# now change the new list and print the original and new list
NewlolList[1][0] = 0
print(" Original: ",lolTestList)
print("     New : ",NewlolList)
print("Both lists are changed.  Bad, very bad!!")
print("I think it should either work or fail but instead it appears copy() returns a reference to the original list")

The copy() method does a shallow copy. Use copy.deepcopy() to recursively copy a container object such as a list.

Thanks for feedback

A huge trap for young players.
If I wanted a reference I would simply assign NewList = OriginalList

To be clear
Solution is to import the copy module

import copy
and use copy.deepcopy(OriginalList)

By default things should work straight from the box. Why have holes for coders to fall into
copy.

In my reality I would make copy() a deepcopy() and if you have a large dataset and want a shallow copy do something else !!!

A little demonstration:

Simple reference:

>>> a = [[1], [2]]
>>> b = a
>>> a is b
True
>>> # a and b are the same object, so any changes made to the contents of one affect both.
>>> b[0] = 0
>>> assert a[0] == 0

Shallow copy:

>>> a = [[1], [2]]
>>> b = a.copy()
>>> a is b
False
>>> # a and b are different objects.
>>> b[0] = 5
>>> assert a[0] == 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
>>> b = a.copy()
>>> a[0] is b[0]
True
>>> # But a[0] and b[0] are still the same object.
>>> b[0][0] = 5
>>> assert a[0][0] == 5

Deep copy:

>>> from copy import deepcopy
>>> b = deepcopy(a)
>>> a[0] is b[0]
False
>>> # a[0] and b[0] are no longer the same object.
>>> b[0][0] = 5
>>> assert a[0][0] == 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Yep, I think every python developer gets bit by this at some point. Another one you’re likely to run into sooner or later is late binding closures:

>>> funcs = [lambda: print(i) for i in range(2)]
>>> for f in funcs:
>>>    f()

What do you think the output will be?

2
2
2