Python Function Arguments (15):避免陷阱,設計更完美的函數

在 Python 中,函數參數的設計非常靈活。

你可以透過位置參數、關鍵字參數、默認參數,甚至可變數量的 *args 和 **kwargs,打造出兼具彈性與可讀性的函數。

然而,這種靈活性同時也伴隨一些陷阱,特別是 可變默認參數,如果不小心踩坑,可能會導致難以追蹤的錯誤。

函數參數的類型

位置參數(Positional Arguments)

根據位置傳遞的參數

def print_name(first_name, last_name):
  print(first_name, last_name)

print_name('Jiho', 'Baggins')

關鍵字參數(Keyword Arguments)

根據名稱傳遞的參數

def print_name(first_name, last_name):
  print(first_name, last_name)

print_name(last_name='Baggins', 
          first_name='Jiho')

默認參數(Default Arguments)

具有預設值的參數

def print_name(first_name='Jiho', 
 last_name='Baggins'):
 print(first_name, last_name)

print_name()

案例情境:學校管理系統

假設我們正在開發一個學校管理系統,需要創建一個函數來生成新學生的記錄:

def createStudent(name, age, grades=[]):
    return {
        'name': name,
        'age': age,
        'grades': grades
    }

這個函數允許我們創建學生記錄,並提供一個空列表作為成績的默認值。

School management system interface showing student records with names, ages, and grades fields

結果

def createStudent(name, age, grades=[]):
 return {
 'name': name,
 'age': age,
 'grades': grades
 }

def addGrade(student, grade):
 student['grades'].append(grade)
 print(student['grades'])

# 創建兩個學生
chrisley = createStudent('Chrisley', 15)
dallas = createStudent('Dallas', 16)

# 添加成績
addGrade(chrisley, 90) # 預期: [90]
addGrade(dallas, 100) # 預期: [100],實際: [90, 100]

Dallas 的成績竟然包含了 Chrisley 的分數!

原因解析

Python 在函數定義時就已經建立了默認參數的物件,而不是在函數被呼叫時重新建立。
因此,grades=[] 在多次呼叫間共用同一個列表,導致出現「資料混用」的錯誤。

解決方案:None 模式

def createStudent(name, age, grades=None):
  if grades is None:
    grades = []
  return {
    'name': name,
    'age': age,
    'grades': grades
  }

這種模式使用 None 作為特殊標記值,在函數內部創建新的列表。

Safe Python code with None default parameter creating a new list each time

測試改進後的解決方案

def createStudent(name, age, grades=None):
  if grades is None:
    grades = []
  return {
    'name': name,
    'age': age,
    'grades': grades
  }

def addGrade(student, grade):
  student['grades'].append(grade)
  print(student['grades'])

# 創建兩個學生
chrisley = createStudent('Chrisley', 15)
dallas = createStudent('Dallas', 16)

# 添加成績
addGrade(chrisley, 90)  # 輸出: [90]
addGrade(dallas, 100)   # 輸出: [100] ✓

現在結果符合預期!每個學生有自己的成績列表。

可變數量的位置參數:*args

def my_function(*args):
  print(args)

my_function('Arg1', 245, False)
# 輸出: ('Arg1', 245, False)

星號操作符 * 允許函數接受任意數量的位置參數,這些參數被打包成一個元組。

args 只是一個慣用名稱,你可以使用任何有效的變數名:

def my_function(*values):
  print(values)
Python function receiving multiple arguments of different types being packed into a tuple

可變數量的關鍵字參數:**kwargs

def print_data(**data):
  print(type(data))
  print(data)
  print(data.get('anything_goes'))

print_data(this_arg='wowzers', 
          anything_goes=101)
# 輸出:
# 
# {'this_arg': 'wowzers', 'anything_goes': 101}
# 101

雙星號操作符 ** 允許函數接受任意數量的關鍵字參數,這些參數被打包成一個字典。

與 *args 類似,kwargs 也只是一個慣用名稱。

組合使用不同類型的參數

def print_animals(animal1, animal2, *args, animal4, **kwargs):
  print(animal1, animal2)
  print(args)
  print(animal4)
  print(kwargs)

print_animals('Snake', 'Fish', 'Guinea Pig', 'Owl', 
              animal4='Cat', animal5='Dog')
# 輸出:
# Snake Fish
# ('Guinea Pig', 'Owl')
# Cat
# {'animal5': 'Dog'}

參數順序非常重要:

  1. 標準位置參數
  2. *args
  3. 標準關鍵字參數
  4. **kwargs

結語

掌握函數參數的使用,不只是能讓你的程式「動得起來」,更能讓它「運行得正確、維護得輕鬆」。

從避免可變默認參數的陷阱,到靈活運用 *args**kwargs,再到結合不同類型的參數設計出高度可擴展的函數,這些技巧能幫助你提升程式的品質與可讀性。

發佈留言

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

返回頂端