使用 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']

這在生成測試數據或填充預設值時非常實用。

例如,要快速創建一個具有重複圖案的列表,列表乘法提供了一個簡單的解決方案。


總結

  1. 單層列表乘法:非常安全且有效,適合用於初始化固定大小的列表,例如 [0] * 10
  2. 二維列表初始化陷阱:避免使用 [[0] * n] * m,因為這會生成多個指向同一列表的引用。
  3. 正確做法:使用列表生成式 [ [0] * n for _ in range(m) ],確保每個子列表都是獨立的。
  4. 靈活應用:列表乘法也能應用於字串列表、布林列表等場景。

小提醒:列表乘法實際上是「複製元素並拼接」,並不是數學上的「相乘」。理解這一點能幫助我們避免不必要的陷阱。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *