Python 函數的參數設計遠比想像中靈活——除了固定的位置參數,還有預設值參數、*args 不定數量位置引數、**kwargs 不定數量關鍵字引數三種進階機制,讓函數能夠因應不同呼叫情境而彈性運作。本文完整解析四種參數類型的語法與混用規則、*args 與 **kwargs 的底層型別、Keyword-Only 參數的強制語法,以及在量化策略函數設計中的實戰應用。
什麼是彈性參數?
一般函數的參數數量在定義時就固定了,呼叫時必須傳入恰好相同數量的引數。但現實中有許多場景需要不固定數量的輸入——例如 print() 可以一次印一個或十個值,sum() 可以接收任意長度的序列。Python 透過 *args 與 **kwargs 兩個特殊語法實現這種彈性,讓函數能夠優雅地處理「我不確定會收到多少個引數」的情況。
可以把 *args 想像成一個可以塞入任意數量原料的麻布袋(最終是 Tuple),**kwargs 則是一本可以寫入任意數量標籤說明的配方本(最終是 Dict),兩者都讓煉金配方在面對不同情況時保持彈性。
核心語法:四種參數類型實戰
# =============================================
# 第一種:位置參數(Positional Arguments)
# =============================================
def add(a, b): # a、b 是位置參數,順序決定對應關係
return a + b
print(add(3, 5)) # 8(a=3, b=5)
print(add(5, 3)) # 8(a=5, b=3,順序不同但結果相同)
# =============================================
# 第二種:預設值參數(Default Parameters)
# =============================================
def calc_profit(buy, sell, shares=100, fee_rate=0.001425):
gross = (sell - buy) * shares
fee = (buy + sell) * shares * fee_rate
return round(gross - fee, 2)
print(calc_profit(100, 120)) # 使用預設 shares=100
print(calc_profit(100, 120, shares=500)) # 關鍵字引數覆蓋預設值
print(calc_profit(100, 120, 200, 0.003))# 全部位置引數傳入
呼叫函數時可以用關鍵字引數(Keyword Arguments)方式傳值:calc_profit(buy=100, sell=120),這樣不需要依照順序,且程式碼更易讀。關鍵字引數也可以只傳部分,其餘使用預設值。
# =============================================
# 第三種:*args(不定數量位置引數)
# =============================================
# *args 把所有多餘的位置引數收集成一個 Tuple
def total(*args):
print(f"收到的引數:{args}") # args 是 Tuple
print(f"型別:{type(args)}")
return sum(args)
print(total(10, 20, 30)) # 收到的引數:(10, 20, 30) → 60
print(total(1, 2, 3, 4, 5)) # 收到的引數:(1, 2, 3, 4, 5) → 15
print(total(100)) # 收到的引數:(100,) → 100
# 混合固定參數與 *args
def log(level, *messages): # level 是固定參數,messages 收集剩餘
for msg in messages:
print(f"[{level}] {msg}")
log("INFO", "系統啟動", "連線成功", "資料載入完成")
# [INFO] 系統啟動
# [INFO] 連線成功
# [INFO] 資料載入完成
*args中的args只是慣例名稱,你可以命名成任何合法變數名(如*numbers、*items),但業界習慣統一用args讓其他人一眼看懂。*args收集到的永遠是 Tuple,即使只傳一個引數也是。
# =============================================
# 第四種:**kwargs(不定數量關鍵字引數)
# =============================================
# **kwargs 把所有多餘的關鍵字引數收集成一個 Dict
def display_info(**kwargs):
print(f"收到的關鍵字引數:{kwargs}") # kwargs 是 Dict
print(f"型別:{type(kwargs)}")
for key, val in kwargs.items():
print(f" {key} = {val}")
display_info(name="AAPL", price=185.2, volume=45000000)
# 收到的關鍵字引數:{'name': 'AAPL', 'price': 185.2, 'volume': 45000000}
# name = AAPL
# price = 185.2
# volume = 45000000
# 混合使用:固定參數 + *args + **kwargs
def build_order(ticker, *quantities, **options):
print(f"標的:{ticker}")
print(f"數量列表:{quantities}") # Tuple
print(f"選項:{options}") # Dict
build_order("AAPL", 100, 200, 300,
order_type="limit", price=185.0, expire="day")
# 標的:AAPL
# 數量列表:(100, 200, 300)
# 選項:{'order_type': 'limit', 'price': 185.0, 'expire': 'day'}
規則與注意事項
| 規則 | 正確範例 | 錯誤範例 / 陷阱 |
|---|---|---|
| 參數順序:位置 → 預設值 → *args → **kwargs | def f(a, b=1, *args, **kwargs): |
def f(*args, a): → 位置參數在 *args 後需用關鍵字傳入 |
| 有預設值的參數必須在無預設值之後 | def f(a, b=1): |
def f(a=1, b): → SyntaxError |
| *args 收集結果是 Tuple(不可變) | for x in args: |
誤以為是 List,試圖 args.append() → AttributeError |
| **kwargs 收集結果是 Dict | kwargs.get('key', default) |
誤以為是普通變數,不用 .items() 迭代 |
| 呼叫時可用 * 解包 List、** 解包 Dict | add(*[3, 5])、f(**{'a':1}) |
忘記解包直接傳 List/Dict → 型別不符 |
常見錯誤與防呆
錯誤一:預設值參數用了可變物件(List / Dict)
# ❌ 陷阱:預設值 List 在所有呼叫間共享,會累積殘留資料
def add_item(item, items=[]): # [] 只建立一次,不是每次呼叫都建新的!
items.append(item)
return items
print(add_item("A")) # ['A']
print(add_item("B")) # ['A', 'B'] ← 殘留上次的 'A'!
print(add_item("C")) # ['A', 'B', 'C'] ← 越來越長!
# ✅ 正確做法:預設值設為 None,函數內再建立新 List
def add_item(item, items=None):
if items is None:
items = [] # 每次呼叫都建立全新的 List
items.append(item)
return items
print(add_item("A")) # ['A']
print(add_item("B")) # ['B'] ← 每次都是全新串列
錯誤二:混用位置引數與關鍵字引數順序錯誤
# ❌ 錯誤寫法(關鍵字引數後面不能再接位置引數)
def order(ticker, price, qty):
return f"{ticker} {price} x {qty}"
# SyntaxError: positional argument follows keyword argument
result = order("AAPL", price=185.0, 100)
# ✅ 正確寫法:關鍵字引數只能放在位置引數之後
result = order("AAPL", 185.0, qty=100) # 正確
result = order("AAPL", price=185.0, qty=100) # 也正確
print(result) # AAPL 185.0 x 100
錯誤三:誤以為 *args 是 List,試圖 append
# ❌ 錯誤寫法(args 是 Tuple,沒有 append 方法)
def collect(*args):
args.append(99) # AttributeError: 'tuple' object has no attribute 'append'
# ✅ 若需要可修改的版本,轉成 List
def collect(*args):
items = list(args) # Tuple 轉 List
items.append(99)
return items
print(collect(1, 2, 3)) # [1, 2, 3, 99]
進階用法
呼叫時解包:* 與 ** 運算子
* 與 ** 不只用在函數定義,在呼叫函數時也可以用來解包序列與字典,把容器中的元素自動展開成對應的引數,非常適合動態建構引數的場景。
def show(a, b, c):
print(f"a={a}, b={b}, c={c}")
# * 解包 List/Tuple 作為位置引數
args_list = [10, 20, 30]
show(*args_list) # a=10, b=20, c=30
# ** 解包 Dict 作為關鍵字引數
kwargs_dict = {'a': 1, 'b': 2, 'c': 3}
show(**kwargs_dict) # a=1, b=2, c=3
# 混合使用
base = [10]
extra = {'b': 20, 'c': 30}
show(*base, **extra) # a=10, b=20, c=30
# 量化應用:動態傳入回測參數
def backtest(ticker, start, end, period=20, threshold=0.03):
print(f"回測 {ticker} | {start}~{end} | period={period} | threshold={threshold}")
params = {
'ticker': 'AAPL',
'start': '2023-01-01',
'end': '2023-12-31',
'period': 14,
'threshold': 0.05
}
backtest(**params) # 動態展開字典為關鍵字引數
Keyword-Only 參數(強制關鍵字傳入)
在 *args 後面定義的參數稱為 Keyword-Only 參數,呼叫時必須用關鍵字傳入,不能用位置引數。這個設計可以強制呼叫者明確指定重要參數名稱,防止因引數順序混淆而造成的錯誤。
# * 後面的參數是 Keyword-Only(必須用關鍵字傳入)
def place_order(ticker, qty, *, order_type, price=None):
print(f"{ticker} x {qty} | 類型:{order_type} | 價格:{price}")
# ✅ 正確:order_type 用關鍵字傳入
place_order("AAPL", 100, order_type="limit", price=185.0)
place_order("TSLA", 50, order_type="market")
# ❌ 錯誤:試圖用位置引數傳入 order_type
# place_order("AAPL", 100, "limit") → TypeError
# 強制關鍵字設計讓呼叫者必須明確寫出 order_type="limit"
# 避免誤把 "limit" 當成第三個位置引數而造成語意錯誤
量化實戰:彈性回測引擎函數
結合 **kwargs 與預設值參數,設計一個可接受任意策略參數組合的彈性回測函數介面,讓參數優化時不需要修改函數簽名。
def run_strategy(ticker, closes, *, strategy="sma_cross", **params):
"""
彈性回測引擎:透過 **params 接收各策略的自訂參數
strategy: 策略名稱(Keyword-Only 強制指定)
**params: 各策略的專屬參數
"""
print(f"執行策略:{strategy} | 標的:{ticker}")
print(f"自訂參數:{params}")
if strategy == "sma_cross":
fast = params.get('fast', 5)
slow = params.get('slow', 20)
print(f" 快線:{fast} | 慢線:{slow}")
elif strategy == "rsi_reversal":
period = params.get('period', 14)
oversold = params.get('oversold', 30)
overbought = params.get('overbought', 70)
print(f" RSI 週期:{period} | 超賣:{oversold} | 超買:{overbought}")
closes = [100, 102, 105, 103, 108, 112, 109, 115]
# 呼叫一:SMA 交叉策略
run_strategy("AAPL", closes, strategy="sma_cross", fast=3, slow=10)
# 呼叫二:RSI 反轉策略
run_strategy("TSLA", closes, strategy="rsi_reversal", period=14, oversold=25)
煉金坊小叮嚀
*args 和 **kwargs 是 Python 函數設計彈性的精華,但初學時不用刻意追求用它們——先把固定參數和預設值用熟,等你遇到「這個函數需要接受不固定數量引數」的真實需求時,自然就會需要它們。最值得銘記的一個坑:預設值絕對不要用可變物件(List、Dict),一律改用 None 作預設值,函數內再初始化——這個陷阱隱蔽、不報錯,但會讓狀態在呼叫之間意外共享,造成難以排查的 bug。在量化開發中,我最常用 **kwargs 設計參數優化介面,讓同一個回測函數可以接受任意策略的自訂參數組合,搭配 .get('key', default) 取值,就能讓每個參數都有合理的預設行為。