A copied table can behave differently depending on which part of it an assignment changes:
original = [[1, 2], [3, 4]]
edited = original.copy()
edited[0] = [9, 9]
print(original)
edited[1][0] = 8
print(original)
[[1, 2], [3, 4]]
[[1, 2], [8, 4]]
The first assignment leaves original alone. The second changes a value in it, although both assignments use edited and the copy was made before either of them.
Editing the rows
Python’s copy documentation describes a shallow copy as a new container holding references to the original contents. For this table, those contents are the two inner lists. edited has its own positions for them, so replacing its first row changes only the copied outer list. The old first row still exists, and original[0] still refers to it. Nothing has changed inside that row.
The second assignment has another index. edited[1] retrieves the second row, which is still shared with original[1]; [0] = 8 changes a position inside that row. Both outer lists refer to the changed row afterwards. This is also why appending a row to edited would leave original alone, while appending a value to edited[1] would change the row seen through both names.
The outer copy is still useful if the work only rearranges rows, removes them, or replaces them wholesale. The inner lists can remain shared for those operations. A table copied for a cell edit has a different requirement: the assignment needs a separate row in which to replace the cell. Copying each row gives that assignment its own list to work on:
original = [[1, 2], [3, 4]]
edited = [row.copy() for row in original]
edited[1][0] = 8
print(original)
print(edited)
[[1, 2], [3, 4]]
[[1, 2], [8, 4]]
The two tables start with equal values, so printing them before the edit would give the same text. After the edit, the second row in edited contains the replacement value and the second row in original retains its previous contents.
Each row contains integers here. Assigning a cell replaces an integer in the copied row, so copying the rows is enough for this example. If a cell instead contained another list, row.copy() would retain a reference to that list. The same question would apply at that next index: which list will the edit actually change? Adding more shallow copies by hand requires knowing where the mutable contents end.
copy.deepcopy(original) recursively copies nested lists. On this table it also gives separate rows, and it remains suitable when there are further lists inside the cells. That can be useful when a whole nested structure needs to be edited independently. But there is another kind of sharing that a deep copy can retain.
Rows created by repetition
The Python Programming FAQ shows a table built using list multiplication. Repeating a list repeats references to its contents. In [[0, 0]] * 2, both positions refer to the same inner list. A deep copy of that table behaves like this:
from copy import deepcopy
original = [[0, 0]] * 2
edited = deepcopy(original)
edited[0][0] = 7
print(original)
print(edited)
print(edited[0] is original[0])
print(edited[0] is edited[1])
[[0, 0], [0, 0]]
[[7, 0], [7, 0]]
False
True
original stayed unchanged, so the deep copy did separate the two tables. Inside edited, though, the rows are still the same list. The copy documentation explains that deepcopy keeps a record of objects already copied during a pass. The shared row gets copied once; when it occurs again, the copy is reused. There was one row object in the input, and there is one copied row object in the result. The two identity checks answer different questions: the first compares across the tables, while the second compares positions within the copied table. A result can pass the first check and still have the sharing shown by the second. Printing the original alone would miss that remaining relationship.
For a table whose rows should vary independently, the FAQ uses a comprehension that creates a fresh list for each row:
edited = [[0, 0] for _ in range(2)]
edited[0][0] = 7
print(edited)
# [[7, 0], [0, 0]]
Here the separate rows exist from the start. For an existing nested list, an is check between the particular rows involved can help establish whether they are shared before choosing a copy operation. A test that changes the same cell the program will change can then check the result, including whether any other row changes with it.
