本文 AI 產出,尚未審核
Python 函式(Functions)
參數類型:位置、具名、預設、*args、**kwargs
簡介
在 Python 中,函式是程式設計的基本建構塊。透過函式,我們可以把重複的程式碼抽象化、提升可讀性、降低維護成本。而 參數的傳遞方式 則是影響函式彈性與可用性的關鍵。
- 位置參數(Positional arguments)讓呼叫者必須依序提供值,最直觀卻也最容易出錯。
- 具名參數(Keyword arguments)則以
name=value的形式指定,提升可讀性。 - 預設參數(Default arguments)讓參數在呼叫時可以省略,為函式提供合理的預設行為。
- 可變長度參數
*args與**kwargs則讓函式接受不確定數量的輸入,從簡單的工具函式到高度通用的 API,都離不開它們的身影。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,完整介紹這五種參數類型,幫助讀者在日常開發中寫出 更易讀、可維護且彈性十足 的 Python 函式。
核心概念
1. 位置參數(Positional Arguments)
位置參數是最基本的參數類型,呼叫函式時必須依照函式定義的順序提供對應的值。
def greet(name, age):
"""簡單的問候函式,使用位置參數"""
print(f"Hello, {name}! 你今年 {age} 歲。")
# 呼叫方式
greet("阿明", 28) # Hello, 阿明! 你今年 28 歲。
# 若順序錯誤,輸出會不符合預期
greet(28, "阿明") # Hello, 28! 你今年 阿明 歲。
Tip:位置參數在參數數量不多且意義明確時最適合使用。
2. 具名參數(Keyword Arguments)
具名參數允許在呼叫時以 參數名=值 的方式指定,順序不再重要,程式碼可讀性大幅提升。
def greet(name, age):
print(f"Hello, {name}! 你今年 {age} 歲。")
# 使用具名參數
greet(age=30, name="小華") # Hello, 小華! 你今年 30 歲。
- 優點:
- 呼叫者可自行決定參數順序。
- 當函式參數較多時,能清楚表達每個值的意義。
3. 預設參數(Default Arguments)
在函式定義時給予參數預設值,呼叫者可以選擇性省略該參數。
def greet(name, age=18):
"""若未提供 age,預設為 18 歲"""
print(f"Hello, {name}! 你今年 {age} 歲。")
greet("阿珍") # Hello, 阿珍! 你今年 18 歲。
greet("阿珍", 22) # Hello, 阿珍! 你今年 22 歲。
注意:預設值會在函式定義時被計算一次,若使用可變物件(如 list、dict)作為預設值,必須特別小心。
4. 可變長度位置參數:*args
*args 讓函式接受任意數量的位置參數,在函式內部會被收集為一個 tuple。
def total(*numbers):
"""計算任意個數字的總和"""
result = sum(numbers)
print(f"總和為 {result}")
total(1, 2, 3) # 總和為 6
total(10, 20) # 總和為 30
total() # 總和為 0
- 應用:
- 建立彈性的統計函式、日誌收集器、指令列參數解析等。
5. 可變長度具名參數:**kwargs
**kwargs 讓函式接受任意數量的具名參數,在函式內部會被收集為一個 dict。
def profile(**info):
"""印出使用者的任意資訊"""
for key, value in info.items():
print(f"{key}: {value}")
profile(name="小美", age=25, city="台北")
# name: 小美
# age: 25
# city: 台北
- 常見用途:
- 建立可擴充的設定函式、封裝第三方 API 的參數、動態生成 SQL 查詢等。
6. 同時使用 *args 與 **kwargs
結合兩者可以同時接受不確定的位置與具名參數。
def logger(level, *messages, **metadata):
"""簡易日誌函式,支援多條訊息與額外資訊"""
print(f"[{level.upper()}] " + " | ".join(messages))
if metadata:
print("Metadata:", metadata)
logger("info", "系統啟動", "載入模組", user="admin", ip="192.168.1.10")
# [INFO] 系統啟動 | 載入模組
# Metadata: {'user': 'admin', 'ip': '192.168.1.10'}
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 預設值使用可變物件 | def f(a=[]): 會導致多次呼叫共享同一個 list。 |
使用 None 作為預設值,然後在函式內部初始化:def f(a=None): a = [] if a is None else a |
| ***args、kwargs 的位置 | *args 必須放在所有位置參數之後,**kwargs 必須放在最後。 |
正確順序:def func(pos1, pos2, *args, kw1=None, **kwargs): |
| 混用位置與具名參數時的衝突 | 同一個參數同時以位置與具名方式傳入會拋出 TypeError。 |
確認呼叫時不要重複指定同一參數。 |
| 過度使用 kwargs 失去型別檢查 | 讓函式變得過於寬鬆,難以追蹤錯誤。 | 若參數明確,盡量使用具名參數或 TypedDict、dataclass 來描述結構。 |
| *args 變成空 tuple | 呼叫時忘記傳入參數,導致 *args 為空,需自行處理。 |
在函式內部使用 if not args: 進行預設行為或拋出自訂例外。 |
最佳實踐
- 先寫位置參數,再加入具名與預設值:保持 API 的簡潔與可讀。
- 預設值盡量使用不可變類型(如
int、str、tuple),避免共享狀態。 - 文件化參數:使用 docstring 說明每個參數的型別與用途,尤其是
*args、**kwargs。 - 適度限制
**kwargs:若只接受特定鍵,使用**kwargs再配合驗證(如if not set(kwargs) <= allowed_keys:)。 - 利用型別提示(type hints):
def func(*args: int, **kwargs: str) -> None:能在 IDE 中即時捕捉錯誤。
實際應用場景
1. 建立通用的 API 請求函式
import requests
def api_get(url, *params, timeout=5, **headers):
"""簡化 GET 請求,支援可變的查詢參數與自訂標頭"""
# 組合查詢字串
query = "&".join(f"{k}={v}" for k, v in enumerate(params, start=1))
full_url = f"{url}?{query}" if query else url
response = requests.get(full_url, timeout=timeout, headers=headers)
return response.json()
# 呼叫範例
data = api_get(
"https://api.example.com/users",
"active", "admin", # *params 變成 ("active", "admin")
timeout=10,
Authorization="Bearer abc123",
Accept="application/json"
)
- 為什麼使用
*params、**headers?*params允許任意數量的查詢條件,未來若需求改變只要多加參數即可。**headers讓呼叫者自行決定要送哪些 HTTP 標頭,保持函式的通用性。
2. 動態產生 SQL 查詢
def select(table, **conditions):
"""根據條件動態產生 SELECT 語句,避免硬編碼"""
if not conditions:
where_clause = ""
else:
placeholders = " AND ".join(f"{col}= %s" for col in conditions)
where_clause = f"WHERE {placeholders}"
sql = f"SELECT * FROM {table} {where_clause};"
values = tuple(conditions.values())
return sql, values
# 使用範例
query, params = select("employees", department="IT", status="active")
print(query) # SELECT * FROM employees WHERE department= %s AND status= %s;
print(params) # ('IT', 'active')
- 好處:利用
**conditions自動映射欄位與值,減少手寫錯誤與 SQL Injection 風險(配合參數化查詢)。
3. 通用的資料驗證函式
def validate(**fields):
"""檢查必填欄位是否為空,回傳錯誤訊息列表"""
errors = []
for name, value in fields.items():
if value in (None, "", [], {}):
errors.append(f"欄位「{name}」不可為空")
return errors
# 呼叫
errs = validate(username="alice", password="", email=None)
# ['欄位「password」不可為空', '欄位「email」不可為空']
- 應用:在表單或 API 請求的前置驗證階段,只要把收到的字典展開傳入即可:
validate(**request.json)。
總結
- 位置參數是最直接的傳值方式,適合參數數量固定且意義明確的情境。
- 具名參數提升呼叫的可讀性,讓程式碼更易於維護。
- 預設參數提供合理的缺省行為,但要避免使用可變物件作為預設值。
*args讓函式接受不定長度的位置參數,常用於統計、日誌、指令列工具等。**kwargs則收集不定長度的具名參數,適合建構彈性的 API、設定或動態查詢。- 同時使用
*args、**kwargs能讓函式兼容各種呼叫方式,但也要小心 過度寬鬆 帶來的型別安全與維護問題。
掌握這五種參數類型的特性與最佳實踐,能讓你在開發 Python 程式時 寫出更清晰、彈性更高且易於擴充 的函式。未來無論是寫小型腳本、建立大型服務,或是設計通用套件,這些概念都是不可或缺的基礎。祝你在 Python 的旅程中玩得開心、寫得更好!