Python
單元:函式(Functions)
主題:函式宣告與呼叫
簡介
在程式設計的世界裡,**函式(function)**是將一段具體的工作抽象化、模組化的關鍵工具。透過函式,我們可以把重複的程式碼集中管理、提升可讀性、降低錯誤率,甚至讓程式的測試與維護變得更輕鬆。
對於 Python 初學者而言,掌握 函式的宣告與呼叫 是邁向「寫得好、寫得快」的第一步;對於已具備基礎的開發者,深入了解參數傳遞機制、預設值與可變參數,則是撰寫彈性、可擴充程式的必要功力。本文將以 實務導向 的方式,從最基本的函式定義說起,逐步帶入常見的技巧與陷阱,讓讀者在閱讀完後能立即在自己的程式中運用。
核心概念
1. 基本函式宣告與呼叫
在 Python 中,使用關鍵字 def 來宣告函式,語法如下:
def 函式名稱(參數1, 參數2, ...):
"""可選的說明文件 (docstring)"""
# 函式本體
執行的程式碼
return 回傳值 # 若未寫 return,預設回傳 None
範例 1:最簡單的 Hello World 函式
def say_hello():
"""印出歡迎訊息"""
print("Hello, Python!")
# 呼叫函式
say_hello()
重點:函式名稱遵循變數命名規則,且 冒號 (:) 為必寫,縮排代表函式的程式區塊。
2. 參數與回傳值
函式可以接受 位置參數(positional arguments),並透過 return 把結果回傳給呼叫端。
範例 2:計算兩數相加
def add(a, b):
"""回傳 a 與 b 的和"""
result = a + b
return result
sum_ = add(3, 5) # 呼叫時傳入 3 與 5
print("3 + 5 =", sum_) # 輸出: 3 + 5 = 8
a、b為形參(formal parameter),在呼叫時傳入的實際值稱為實參(actual argument)。return後的表達式會立即結束函式,並把值交給呼叫處;若省略return,函式預設回傳None。
3. 預設參數與關鍵字參數
為了提升函式的彈性,Python 允許在宣告時為參數設定預設值,呼叫時可以省略該參數;同時也支援關鍵字參數(keyword arguments),讓參數的順序不再重要。
範例 3:帶預設值的函式
def greet(name, greeting="哈囉"):
"""根據提供的名字與問候語產生訊息"""
print(f"{greeting},{name}!")
greet("小明") # 使用預設的 greeting,輸出: 哈囉,小明!
greet("阿美", greeting="早安") # 使用關鍵字參數,輸出: 早安,阿美!
greet(greeting="晚安", name="大雄") # 參數順序任意,輸出: 晚安,大雄!
技巧:預設參數必須放在 非預設參數之後,否則會產生
SyntaxError。
4. 可變參數(*args, **kwargs)
有時候我們無法預先知道會傳入多少參數,這時可使用 *args(收集位置參數)與 **kwargs(收集關鍵字參數)來彈性接收。
範例 4:統一計算任意個數的總和
def total(*numbers):
"""接受任意個數的數字,回傳它們的總和"""
sum_ = 0
for n in numbers:
sum_ += n
return sum_
print(total(1, 2, 3)) # 6
print(total(10, 20, 30, 40)) # 100
範例 5:把字典解包成關鍵字參數
def show_profile(name, age, **extra):
"""印出基本資料,並顯示額外資訊"""
print(f"姓名: {name}, 年齡: {age}")
for key, value in extra.items():
print(f"{key}: {value}")
profile = {"city": "台北", "hobby": "登山"}
show_profile("小芳", 28, **profile)
# 輸出:
# 姓名: 小芳, 年齡: 28
# city: 台北
# hobby: 登山
*numbers變成一個 tuple,**extra變成一個 dict。- 在呼叫端使用
*或**可以解包序列或字典,讓參數傳遞更自然。
5. 匿名函式(lambda)
當需要簡短的單行函式時,lambda 表達式提供了快速的寫法,常見於 map()、filter()、sorted() 等高階函式中。
範例 6:使用 lambda 排序字串長度
words = ["Python", "資料科學", "AI", "機器學習"]
# 依字串長度由短到長排序
sorted_words = sorted(words, key=lambda w: len(w))
print(sorted_words) # ['AI', 'Python', '資料科學', '機器學習']
注意:
lambda只能寫單行表達式,若邏輯較複雜,仍建議使用def定義完整函式,以提升可讀性。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 建議的做法 |
|---|---|---|
| 可變物件作為預設參數 | 每次呼叫函式時,共用同一個物件,導致意外的狀態累積 | 使用 None 作為預設值,並在函式內部初始化:def func(arg=None): if arg is None: arg = [] |
忘記 return |
函式只執行副作用,呼叫端拿到 None,可能導致後續錯誤 |
確認函式的設計是否需要回傳值,若需要,務必寫 return;若不需要,使用明確的說明文件或 pass 表示空操作 |
| 參數順序混淆 | 位置參數與關鍵字參數混用時,可能拋出 TypeError |
優先使用關鍵字參數,或在函式文件中明確列出參數順序與說明 |
過度使用 *args/**kwargs |
失去函式的可讀性與自我說明性 | 僅在真的需要接受不確定數量參數時使用,平時盡量寫明確的參數列表 |
| lambda 內部寫太多 | 讓程式碼變得難以閱讀與除錯 | 保持 lambda 簡潔,複雜邏輯拆成普通函式 |
最佳實踐
- 為每個函式寫 docstring:使用三引號撰寫說明,說明參數、回傳值與例外情況。
- 遵守 PEP 8 命名規範:函式名稱使用小寫加底線(snake_case)。
- 盡量讓函式保持單一職責(Single Responsibility Principle),每個函式只解決一件事。
- 使用型別註解(type hints)提升 IDE 輔助與程式可讀性:
def add(a: int, b: int) -> int: return a + b - 單元測試:為關鍵函式撰寫測試,確保未來改動不會破壞原有行為。
實際應用場景
資料前處理
在資料科學專案中,常需要將 CSV 檔案的每一列資料轉換成特定格式。利用def clean_row(row):把清理邏輯封裝,配合map(clean_row, rows)可一次處理大量資料。API 請求封裝
為了避免在程式的每個角落重複寫requests.get(url, headers=...),可以寫一個def fetch_json(endpoint, **params):的通用函式,統一處理錯誤、重試機制與回傳格式。GUI 事件處理
使用tkinter時,按鈕的command參數需要傳入一個可呼叫的函式。將每個按鈕的行為抽成獨立函式(或使用lambda包裝參數),可以讓 UI 程式碼保持乾淨。自訂排序與過濾
在電商平台的商品列表中,常需要依「價格」或「評分」排序。利用sorted(products, key=lambda p: p['price']),或寫一個def sort_by(key): return lambda item: item[key],讓排序邏輯可重用。遞迴演算法
計算階乘、斐波那契數列或樹狀結構遍歷,都離不開函式的遞迴呼叫。適當的基礎條件(base case)與參數傳遞,是避免無限遞迴的關鍵。
總結
函式是 Python 程式設計的基石,宣告 時使用 def、呼叫 時只要提供相對應的參數,就能把複雜的工作分割成易於管理的小單元。掌握以下幾點,能讓你的程式更可讀、可維護、可擴充:
- 清晰的 函式名稱 與 docstring,讓使用者一眼看懂功能。
- 正確使用 位置參數、關鍵字參數、預設參數,提升呼叫的彈性。
- 在需要時善用 *args、**kwargs 與 lambda,但別濫用以免失去可讀性。
- 注意 可變預設值 的陷阱,並遵循 PEP 8 與型別註解的最佳實踐。
只要在寫程式的每一步都把 「把工作拆成小函式」 當成思考模式,你將會發現 Python 的開發效率與程式品質會有顯著的提升。祝你在 Python 的函式世界裡玩得開心、寫得順手!