Python 預設值與 args、kwargs 彈性參數教學

Python 函數的參數設計遠比想像中靈活——除了固定的位置參數,還有預設值參數、*args 不定數量位置引數、**kwargs 不定數量關鍵字引數三種進階機制,讓函數能夠因應不同呼叫情境而彈性運作。本文完整解析四種參數類型的語法與混用規則、*args**kwargs 的底層型別、Keyword-Only 參數的強制語法,以及在量化策略函數設計中的實戰應用。

Cinematic dark alchemy forge with a mystical function capsule accepting variable numbers of glowing ingredient orbs entering through expandable input ports, some labeled with rune keys representing kwargs, amber firelight and deep shadows on ancient stone walls, flexible arcane mechanism, cinematic lighting, 16:9

什麼是彈性參數?

一般函數的參數數量在定義時就固定了,呼叫時必須傳入恰好相同數量的引數。但現實中有許多場景需要不固定數量的輸入——例如 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) 取值,就能讓每個參數都有合理的預設行為。

張貼留言

較新的 較舊