你在寫 Python 函式時,是不是曾經寫過類似這樣的程式碼,想說「如果呼叫時沒有傳入清單,就預設給一個空的 `[]`」?
def append_to_list(value, my_list=[]):
my_list.append(value)
return my_list看起來非常直覺對吧?但當我們實際執行幾次時,卻發生了恐怖的事情:
print('第一次呼叫:', append_to_list(1))
print('第二次呼叫:', append_to_list(2))
print('第三次呼叫:', append_to_list(3))**【預期結果】**
第一次呼叫: [1]
第二次呼叫: [2] <-- 因為沒有傳 my_list,原本以為會重新拿到 []
第三次呼叫: [3]**【實際結果】**
第一次呼叫: [1]
第二次呼叫: [1, 2]
第三次呼叫: [1, 2, 3] <-- 什麼?!為什麼前面的資料還在?
這究竟是怎麼回事?難道是 Python 當機了嗎?
其實,這是 Python 裡最經典也最常考的面試題之一:
**「可變預設參數陷阱 (Mutable Default Argument)」**。
## 🔍 兇手抓到了:預設值只在「函式定義」時建立一次!
要理解這個現象,我們必須知道 Python 直譯器 (Interpreter) 運作的一個重要機制:
> **函式的預設值 (Default Arguments),是在 Python 讀到 `def` 這行程式「定義函式時」被建立並綁定的,而不是「每次呼叫函式時」重新建立!**
讓我們用慢動作重播剛剛發生了什麼事:
1. **定義期 (Definition Time):**
當 Python 讀到 `def append_to_list(value, my_list=[]):` 時,它在記憶體中(假設地址是 `0x1000`)建立了一個空清單 `[]`,並且把它掛在 `append_to_list` 這個函式的身上當作預設值。
2. **第一次呼叫 – `append_to_list(1)`:**
你沒有傳入 `my_list`,所以 Python 拿出了 `0x1000` 這個清單。
執行 `.append(1)` 後,`0x1000` 變成了 `[1]`。
3. **第二次呼叫 – `append_to_list(2)`:**
注意這裡!因為預設值並不會重新計算,Python **再次拿出了同一個** `0x1000` 的清單。
但此時它裡面已經裝了 `[1]` 啦!
執行 `.append(2)` 後,它理所當然變成了 `[1, 2]`。
因為 List (清單) 和 Dictionary (字典) 在 Python 中都是 **可變物件 (Mutable)**,只要你在函式內修改了它,這個修改就會永遠留存在函式身上,汙染了後續所有的呼叫。
—
## 🛠️ 解決方案:標準防呆寫法
知道原因後,解法其實很簡單:
**永遠不要把可變物件 (如 `[]`, `{}`) 寫在預設值裡。改用不可變的 `None` 取代!**
標準的正確寫法如下:
def append_to_list(value, my_list=None):
# 每次呼叫時才作檢查,動態創造全新的清單
if my_list is None:
my_list = []
my_list.append(value)
return my_list我們再執行一次看看:

### 為什麼換成 `None` 就沒事了?
因為 `None` 是一個**不可變物件 (Immutable)**。每當你呼叫函式而沒有傳入 `my_list` 時,變數會先被指派為 `None`,然後進到 `if` 判斷式中。此時執行的 `my_list = []`,是在**函式呼叫的當下**「全新產生」的獨立清單,它們彼此擁有獨立的記憶體位置,再也不會互相干擾了。
—
## 💡 總結
下次寫 Python 函式時,請在心裡默念:
1. **預設參數只會被評估一次。**
2. **看到 `def fn(data=[])` 或 `def fn(data={})`,你的雷達就要響起!**
3. **一律改寫成 `def fn(data=None)`。**
這不僅能避免靈異現象般的 Bug,
也能讓你的程式碼看起來更專業、
更符合 Python 的標準規範 (Idiomatic Python) 喔!
推薦hahow線上學習python: https://igrape.net/30afN



![Python: List[ pandas.Series ] 轉DataFrame技巧:正確理解row和column的關係,同 concat( List[ pandas.Series ], axis=1 ).T Python: List[ pandas.Series ] 轉DataFrame技巧:正確理解row和column的關係,同 concat( List[ pandas.Series ], axis=1 ).T](https://i2.wp.com/savingking.com.tw/wp-content/uploads/2025/04/20250422150133_0_1cfa94.png?quality=90&zoom=2&ssl=1&resize=350%2C233)



![Python: matplotlib如何控制legend的位置? ax.legend(handles=[patch], loc=’upper left’, bbox_to_anchor=(6/10, 3/5) Python: matplotlib如何控制legend的位置? ax.legend(handles=[patch], loc=’upper left’, bbox_to_anchor=(6/10, 3/5)](https://i2.wp.com/savingking.com.tw/wp-content/uploads/2023/05/20230502163945_79.png?quality=90&zoom=2&ssl=1&resize=350%2C233)


近期留言