Python 靈異事件:為什麼我只改了一個格子,整排都跟著變了?
如果你嘗試在 Python 中建立一個 3x3 的九宮格(二維列表),你可能會很直覺地寫出這行程式碼:
# 建立一個 3x3 的二維列表,初始值都是 0
grid = [[0] * 3] * 3
看起來很完美對吧?印出來也是漂亮的 [[0, 0, 0], [0, 0, 0], [0, 0, 0]]。但神奇的事情發生了,當你只想把「左上角」的格子改成 1 時:
grid[0][0] = 1
print(grid)
預期結果: [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
實際結果: [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
「等一下,我明明只改了第一列,為什麼每一列的第一個數字都變成了 1?」別驚慌,這不是 Python 的 Bug,而是它為了節省體力,在背後玩了一個「分身術」。
核心祕密:它是「同一個房間」的三把鑰匙
在上一篇我們聊過 is 和 == 的差別,知道 Python 的變數有時候是指向同一個記憶體位址。這個問題的根源就在這裡。
當你寫 [0] * 3 時,Python 確實幫你建立了三個獨立的 0,變成 [0, 0, 0]。
但當你接著寫 [...] * 3 時,Python 為了偷懶(優化效能),它並不會真的去「影印」三份內容一模一樣的列表,而是複製了三份「遙控器」。
想像一下:
1. 你先蓋了一個房間(內層列表:[0, 0, 0])。
2. 你把這個房間重複了三次放在一個大箱子裡。
3. 關鍵點: Python 並沒有蓋出三間長得一樣的房間,它只是給了你三把「同一間房間」的鑰匙。
所以,不論你用哪一把鑰匙進去改裝房間(修改 grid[0]、grid[1] 或 grid[2]),因為大家進去的都是同一個房間,所以當你從另一扇門看進去時,房間已經被改掉了。
為什麼 [0] * 3 就不會出事?
你可能會問:「那為什麼 [0, 0, 0] 裡面的三個 0 不會互相影響?」
這是因為 Python 中的數字(Integer)是「不可變的」(Immutable)。當你把第一個 0 改成 1 時,Python 並不是把 0 抹掉寫成 1,而是直接把那格的位置指向一個新的數字 1。
但列表(List)是「可變的」(Mutable)。當你執行 grid[0][0] = 1 時,你是進入了那個列表「內部」去修改內容,而所有指向這個列表的變數,都會看到這個變動。
那該怎麼辦?正確的「影印」方式
如果你想要建立一個互不干擾、各自獨立的九宮格,你需要讓 Python 真的去「蓋三間新房子」。最常見的做法是使用 「列表生成式 (List Comprehension)」:
# 正確的做法
grid = [[0] * 3 for _ in range(3)]
這行程式碼的意思是:「請幫我跑 3 次迴圈,每一次都重新建立一個全新的 [0, 0, 0] 列表。」
這樣一來,每一列都是獨立的實體,你有三間獨立的房間,改了第一間,第二、三間絕對不會跟著變。
總結
[[0] * 3] * 3 是一個非常經典的 Python 陷阱。它教了我們重要的一課:
- 乘法運算子
*用在列表時,複製的是「引用(Reference)」,而不是內容。 - 如果你處理的是會被修改的對象(像是列表、字典),請改用 迴圈 或 列表生成式 來確保每個元素都是獨立的。
下次看到你的資料「牽一髮而動全身」時,記得檢查一下,你是不是不小心給了 Python 太多的「分身鑰匙」囉!
0 留言
發表留言