用 list.copy() 備份串列之後,可以繼續在副本上處理資料。當串列裡還有串列,備份後就有兩個地方可以修改:外面的串列,以及裡面的串列。

先不換掉裡面的串列,只在末端加入資料:

rows = [[1], [2]]
backup = rows.copy()

print(backup is rows)        # False
print(backup[0] is rows[0])  # True

backup[0].append(9)
print(rows)                 # [[1, 9], [2]]

is 比較的是物件身分(可參考 Python 的 is 和 == 差在哪)。外面的兩個串列不同,但索引 0 取得的仍是同一個內部串列。append(9) 修改了裡面的內容,從 rows 取得它時,數字 9 也在裡面。

Python 的 copy 文件把這種方式稱為淺拷貝(shallow copy):建立新的容器,再放入對原有元素的參照。複製後的外部串列可以各自增減元素,原有元素本身則繼續共用。

接著在同一次執行中,把 backup[0] 換掉:

backup[0] = [7]
print(backup)  # [[7], [2]]
print(rows)    # [[1, 9], [2]]

指定 backup[0] = [7],會把 backup 索引 0 的元素換成新的串列。rows 並沒有做這個替換,所以保留剛剛被加入 9 的串列。兩種操作都寫了 backup[0],但 append 會修改取得的串列,指定新值則會替換外部串列的元素。

需要獨立的內部資料時

如果備份後還要修改內部串列,而且希望原資料維持原樣,可以在修改前使用 deepcopy()。以下重新建立資料:

from copy import deepcopy

rows = [[1], [2]]
backup = deepcopy(rows)
backup[0].append(9)

print(rows)    # [[1], [2]]
print(backup)  # [[1, 9], [2]]

深拷貝會遞迴複製內部物件,對這組純串列資料來說,修改 backup[0] 已經不會影響 rows[0]。不過這個做法也會複製原本可能想共用的資料,並不需要為了備份就一律使用。

像是先複製清單,再從副本刪掉不需要的項目,沒有修改保留下來的內部資料,淺拷貝就能讓兩份清單各自增減。是否需要深拷貝,可以從後續實際會修改哪些資料來決定。