
在程式設計中,複製貼上 (Copy-Paste) 是萬惡之源。函數 (Functions) 是實現 DRY (Don’t Repeat Yourself) 原則的第一步。它允許我們將一段邏輯封裝起來,並透過參數化來重複利用。
本章將從函數的解剖學開始,深入探討參數傳遞的藝術、變數作用域 (Scope) 的規則,以及 Python 特有的 Lambda 表達式。
1. 函數解剖學 (The Anatomy of a Function)
一個標準的 Python 函數包含定義 (def)、名稱、參數列表、文件字串 (Docstring) 與回傳值。
def calculate_price(price: float, tax_rate: float = 0.05) -> float:
"""
計算含稅價格。
Args:
price (float): 原始價格
tax_rate (float): 稅率 (預設 5%)
Returns:
float: 含稅總價
"""
total = price * (1 + tax_rate)
return total
為什麼需要 Docstrings?
Python 的 docstring (三引號字串) 不僅是註解,它是函數的一部分。使用 help(calculate_price) 或在 IDE 中懸停時,都會顯示這段文檔。這是專業開發者的標準配備。
2. 參數傳遞的藝術
Python 的參數機制非常靈活,掌握它可以讓 API 設計更優雅。
2.1 位置參數 vs 關鍵字參數
def greet(name, message):
print(f"{message}, {name}!")
# 位置引數 (Positional Argument) - 順序重要
greet("Alice", "Hello")
# 關鍵字引數 (Keyword Argument) - 順序不重要,可讀性高
greet(message="Hi", name="Bob")
2.2 *args 與 **kwargs: 無限可能的參數
當你不確定函數會接收多少參數時,這兩個魔法符號就派上用場了。
*args: 接收任意數量的位置參數,打包成 Tuple。**kwargs: 接收任意數量的關鍵字參數,打包成 Dictionary。
def superhero_profile(name, *powers, **details):
print(f"英雄: {name}")
print(f"能力: {powers}") # Tuple: ('Flight', 'Strength')
print(f"詳細資料: {details}") # Dict: {'age': 30, 'city': 'NY'}
superhero_profile("Superman", "Flight", "Strength", age=30, city="Metropolis")
2.3 強制關鍵字參數 (Keyword-Only Arguments)
在 Python 3 中,我們可以使用 * 來強制某些參數必須使用關鍵字傳遞,這在設定選項較多的函數中非常有用,能避免參數順序錯誤。
# `notify` 必須用關鍵字傳遞
def create_user(username, *, notify=True, content=""):
print(f"建立使用者 {username}, 通知: {notify}")
create_user("Alice", notify=False) # 正確
# create_user("Alice", False) # 錯誤!TypeError
3. 變數作用域 (Scope) 與 LEGB 規則
Python 解析變數名稱時,是依照 LEGB 的順序搜尋:
- L (Local): 函數內部的區域變數。
- E (Enclosing): 外部巢狀函數的變數 (Closure)。
- G (Global): 模組層級的全域變數。
- B (Built-in): Python 內建名稱 (如
print,len)。
global 與 nonlocal 關鍵字
若要在函數內修改外部變數,需明確宣告。
counter = 0
def increment():
global counter # 宣告我要修改全域變數
counter += 1
def outer():
x = 10
def inner():
nonlocal x # 宣告我要修改 Enclosing 變數
x = 20
inner()
4. Lambda 表達式:匿名函數
Lambda 是一種定義單行、小型函數的語法糖,常用於高階函數 (如 map, filter, sorted) 中。
語法: lambda arguments: expression
# 傳統寫法
def square(x):
return x ** 2
# Lambda 寫法
square_lambda = lambda x: x ** 2
# 實戰應用:依據字典的某個鍵值排序
users = [{'name': 'A', 'age': 30}, {'name': 'B', 'age': 20}]
sorted_users = sorted(users, key=lambda u: u['age'])
5. 一級公民 (First-Class Citizen)
在 Python 中,函數是「一級公民」,這意味著:
- 函數可以被指派給變數。
- 函數可以作為參數傳遞給其他函數。
- 函數可以是另一個函數的回傳值。
這為 裝飾器 (Decorators) 等進階模式奠定了基礎(我們將在進階章節深入探討)。
def apply_operation(func, x, y):
return func(x, y)
def add(a, b): return a + b
def sub(a, b): return a - b
print(apply_operation(add, 5, 3)) # 傳遞函數作為參數
6. 常見問題 (FAQ)
Q1: 什麼時候該用 Lambda?
A: 當你需要一個簡單的、一次性的函數,且內容只有一行表達式時 (如 sorted 的 key)。如果邏輯複雜或需要重複使用,請使用標準 def 定義,以提升可讀性。
Q2: 預設參數的陷阱是什麼?
A: 永遠不要使用可變物件 (如 list, dict) 作為預設參數! 預設參數只會在函數定義時被建立一次,後續呼叫會共用同一個物件。
# ❌ 危險寫法
def append_item(item, list=[]):
list.append(item)
return list
print(append_item(1)) # [1]
print(append_item(2)) # [1, 2] !? 竟然共用了
# ✅ 正確寫法
def append_item(item, list=None):
if list is None:
list = []
list.append(item)
return list
Q3: return 與 print 有何不同?
A: print 只是將結果顯示在螢幕上,對程式邏輯沒有後續影響;return 則是將值回傳給呼叫者,可以將結果存入變數或進行後續運算。函數若沒有寫 return,預設回傳 None。
7. 總結
函數是構建大型程式的磚塊。掌握參數的靈活運用與作用域規則,能讓你寫出更健壯、可維護的程式碼。
本章重點回顧:
- DRY 原則: 封裝重複邏輯。
- Docstrings: 為函數撰寫文件是好習慣。
*args/**kwargs: 處理彈性參數。- LEGB: 理解變數搜尋路徑。
- Lambda: 簡潔的匿名函數。
在下一章中,我們將探討 資料結構 (Data Structures),學習如何使用 List, Dict, Set 來有效地組織與管理數據!
延伸閱讀: