使用 Python 列表(串列)乘法:從入門到進階
更新日期: 2025 年 3 月 8 日
在 Python 中,列表(或稱串列,List)是一種非常靈活且常用的數據結構。
我們經常需要快速初始化列表或重複元素,此時「乘法運算符 *
」便派上用場。
然而,這個操作背後也藏有一些陷阱,特別是在二維列表的操作中。
本篇文章將帶領初學者深入了解列表乘法的各種用法與潛在問題,並提供正確的操作方法。
單層列表的乘法
單層列表(即一維列表)使用乘法運算符非常直觀且安全,可以快速生成重複元素的列表,尤其適合在初始化時使用。
單層列表乘法示例
lst = [0] * 5
print(lst) # 輸出: [0, 0, 0, 0, 0]
在這個示例中,[0] * 5
會將單一元素 [0]
重複 5 次,生成一個長度為 5、每個元素都是 0
的列表。這種方法適合用來初始化固定大小且初始值相同的列表,比如需要一個全是 False
的布林列表:
flags = [False] * 10
print(flags)
# 輸出: [False, False, False, False, False, False, False, False, False, False]
二維列表的乘法:陷阱與問題
在初始化二維列表(或更高維度列表)時,直接使用乘法運算符可能會導致意想不到的行為。
問題示例
dp = [[0] * 2] * 3
print(dp)
# 輸出: [[0, 0], [0, 0], [0, 0]]
看似正常,但問題出在「多個子列表實際上是同一個列表的引用」,這會導致修改一個子列表時,其他子列表也被影響:
dp[0][0] = 1
print(dp)
# 輸出: [[1, 0], [1, 0], [1, 0]]
問題的根源
當使用 [[0] * 2] * 3
這種方式時,Python 並沒有創建三個獨立的子列表,而是複製了同一個子列表的引用。
因此,這三個子列表指向同一個記憶體地址,修改其中一個子列表會同步影響所有子列表。
flowchart TD subgraph method1["方法 1: dp = [[0] * 2] * 3"] A1[dp] --> B1["[0, 0]"] A1 --> B2["[0, 0]"] A1 --> B3["[0, 0]"] %% 表示引用關係的虛線 B1 -.-> C1((同一個列表物件)) B2 -.-> C1 B3 -.-> C1 %% 修改後的狀態 D1["修改 dp[0][0] = 1 後"] D1 --> E1["[1, 0]"] D1 --> E2["[1, 0]"] D1 --> E3["[1, 0]"] %% 表示所有行的值都被修改 E1 -.-> F1(("所有行都被修改<br>因為指向同一物件")) E2 -.-> F1 E3 -.-> F1 end
正確初始化二維列表的方法
為了避免上述問題,應該使用「列表生成式」(List Comprehension),確保每個子列表都是獨立的實例:
dp = [[0] * 2 for _ in range(3)]
print(dp)
# 輸出: [[0, 0], [0, 0], [0, 0]]
這樣的寫法會確保每次迭代時都創建一個全新的子列表。修改其中一個子列表後,其他子列表不會受到影響:
dp[0][0] = 1
print(dp)
# 輸出: [[1, 0], [0, 0], [0, 0]]
為何列表生成式有效?
for _ in range(3)
每次迭代都會執行[0] * 2
,創建一個新的列表。for _
中的_
是一個約定成俗的變數名稱,表示這個變數僅用於迴圈,不會在後續程式中使用。- 每個子列表擁有不同的記憶體地址,確保數據獨立性。
flowchart TD subgraph method2["方法 2: dp = [[0] * 2 for _ in range(3)]"] A2[dp] --> G1["[0, 0]"] A2 --> G2["[0, 0]"] A2 --> G3["[0, 0]"] %% 表示獨立的列表物件 G1 -.-> H1((獨立列表物件 1)) G2 -.-> H2((獨立列表物件 2)) G3 -.-> H3((獨立列表物件 3)) %% 修改後的狀態 I1["修改 dp[0][0] = 1 後"] I1 --> J1["[1, 0]"] I1 --> J2["[0, 0]"] I1 --> J3["[0, 0]"] %% 表示只有第一行被修改 J1 -.-> K1(("只有目標行被修改<br>因為是獨立物件")) end
列表乘法的其他應用場景
除了初始化數字列表或布林列表,列表乘法還有一些靈活的應用場景,例如重複字串列表:
chars = ["a", "b"] * 3
print(chars)
# 輸出: ['a', 'b', 'a', 'b', 'a', 'b']
這在生成測試數據或填充預設值時非常實用。
例如,要快速創建一個具有重複圖案的列表,列表乘法提供了一個簡單的解決方案。
總結
- 單層列表乘法:非常安全且有效,適合用於初始化固定大小的列表,例如
[0] * 10
。 - 二維列表初始化陷阱:避免使用
[[0] * n] * m
,因為這會生成多個指向同一列表的引用。 - 正確做法:使用列表生成式
[ [0] * n for _ in range(m) ]
,確保每個子列表都是獨立的。 - 靈活應用:列表乘法也能應用於字串列表、布林列表等場景。
小提醒:列表乘法實際上是「複製元素並拼接」,並不是數學上的「相乘」。理解這一點能幫助我們避免不必要的陷阱。