理解 Python 函數中的預設值機制
更新日期: 2025 年 1 月 23 日
本文為 python 打包與解構 系列文,第 1 篇:
- 理解 Python 函數中的預設值機制 👈進度
- Python 函數參數詳解:位置引數與關鍵字引數的新手指南
- 理解 Python 函數中的 / 與 * 用法
- Python 串列開箱(Unpacking)詳解:新手指南
- Python 字典的開箱(**)詳解:新手指南
在 Python 中,函數的參數可以指定預設值,這讓函數更靈活易用。
然而,當預設值為可變物件(如串列或字典)時,可能會導致意外的行為。
本文將詳細解析這一現象,並提供正確的使用方法。
函數預設值的機制
Python 的函數預設值是在函數定義時計算並儲存的,且該值在後續的函數調用中會被重複使用。
如果預設值是一個可變物件(如串列、字典),則對該物件的修改會影響後續調用。
範例:預設值為串列的問題
def hi(a, num=[]): # 預設值 num 是一個空串列
num.append(a)
print(num)
hi(1) # 輸出 [1]
hi(2) # 輸出 [1, 2]
hi(3) # 輸出 [1, 2, 3]
行為分析
預期輸出應為:
[1]
[2]
[3]
但實際輸出卻是:
[1]
[1, 2]
[1, 2, 3]
原因是 num
的預設值 []
在函數定義時被初始化,並在後續調用中共享同一個物件。
每次調用函數時,num.append(a)
都會修改同一個串列。
機制解析
函數的預設值是在定義階段,儲存在記憶體中的。
當使用預設值時,函數調用不會重新初始化該值,而是引用已有的物件。
流程圖解釋
函數定義階段:
num --> 空串列 []
第一次調用 hi(1):
num 引用 --> [1]
第二次調用 hi(2):
num 引用 --> [1, 2]
第三次調用 hi(3):
num 引用 --> [1, 2, 3]
解決方法
可以將預設值設為 None
,在函數內部進行檢查並初始化:
def hi(a, num=None):
if num is None:
num = [] # 每次都創建一個新串列
num.append(a)
print(num)
hi(1) # 輸出 [1]
hi(2) # 輸出 [2]
hi(3) # 輸出 [3]
範例 2:預設值為隨機數的問題
import random
def hi(n, m=random.randint(1, 100)): # 預設值 m 是隨機數
print(n + m)
hi(1)
hi(1)
hi(1)
行為分析
預期輸出應該是每次 m
都是新的隨機值,但實際輸出如下:
93 (一個隨機數字)
93
93
原因解析
與串列的情況類似,m
的預設值在函數定義階段已經計算並儲存,後續調用使用的都是同一個固定的值。
解決方案
與前述方法一致,可將預設值設為 None
,在函數內部進行初始化:
import random
def hi(n, m=None):
if m is None:
m = random.randint(1, 100) # 每次調用重新生成隨機數
print(n + m)
hi(1) # 隨機輸出,如 42
hi(1) # 隨機輸出,如 57
hi(1) # 隨機輸出,如 89
注意事項與建議
- 避免可變物件作為預設值:串列、字典等可變物件應該避免直接作為函數參數的預設值。
- 使用
None
作為哨兵值:將預設值設為None
,在函數內部進行初始化,確保每次調用不共享同一物件。 - 對函數的預設值保持謹慎:特別是在涉及隨機數或需要獨立記憶體空間的場景中,務必檢查預設值的行為。
結語
Python 函數的預設值機制雖然靈活,但其特性可能導致意外的行為,尤其是當預設值為可變物件時。
理解預設值的初始化時機以及共享記憶體的特性,能幫助開發者避免相關錯誤。
在實際使用中,推薦使用 None
作為哨兵值,結合內部初始化邏輯,確保函數行為的正確性與可預測性。