Python講座 / 可変長引数と*・**による展開を使う

関数定義の*argsは追加の位置引数をタプル、**kwargsは追加のキーワード引数を辞書へ集めます。呼び出し側の*・**は反対に、既存コレクションを引数へ展開する書き方です。

集める構文と展開する構文を区別する

argsとkwargsは慣用的な名前で、働きを決めるのはスターの構文です。入力が固定されている関数まで可変長にすると、受け付ける値や誤りが見えにくくなる場合があります。

場所 式 動作
定義 def f(*args) 追加の位置引数を集める
定義 def f(**kwargs) 追加のキーワード引数を集める
呼び出し f(*values) 反復可能な値を位置引数へ展開する
呼び出し f(**options) 文字列キーの対応をキーワードへ展開する

デフォルト引数とラッパーの転送は、位置引数・キーワード引数・既定値を使うで扱う規則とつながります。

金額集計とオプション表示を実行する

varargs.pyへ全体コードを保存し、仮想環境でpython varargs.pyを実行します。

def total(*amounts, fee=0):
    return sum(amounts) + fee

def describe(**fields):
    return ", ".join(f"{key}={value}" for key, value in sorted(fields.items()))

values = [3500, 8000, 2500]
print(total(*values, fee=500))
options = {"category": "food", "currency": "KRW"}
print(describe(**options))

予想結果です。手数料は名前で渡し、説明用の項目はキー順に並べています。

14500
category=food, currency=KRW

重複キーワードと意図しない展開を防ぐ

**で展開するキーは文字列で、呼び出し先が受け付ける引数名である必要があります。f(amount=1, **{"amount": 2})は同じ引数を二回指定するためTypeErrorになります。辞書をマージするときと、呼び出し引数を重複させるときの規則は同じではありません。

文字列を*で展開すると一文字ずつ渡します。total(*"123")は整数123を渡すことにはなりません。一つのコレクションが明確な意味を持つなら、任意個の位置引数より、リスト一つを受ける普通の引数の方が伝わりやすい場合もあります。

受け付ける入力を管理する

**kwargsを受け付けても、外部入力を無検証で別のAPIやSQLへ転送してよいわけではありません。必須項目と許可するオプション名を検証します。つづりを間違えた設定まで黙って受けると、原因を追いにくくなります。

valuesへ0を足して合計が変わらないこと、feeを省略すると既定の0になることを確認します。同じキーワードを明示と展開の両方で渡す失敗も試してください。可変長が問題の性質に合う場合に使い、意味を見せたいオプションには普通の名前付き引数を残します。

ラッパーの def wrapper(*args, **kwargs) は位置引数とキーワード引数を集め、内部の func(*args, **kwargs) はそれらを展開して渡します。辞書を位置引数一つとして渡す場合とは異なります。同じ引数を二重に指定するとTypeErrorになります。

関連する公式リファレンス