Q1次のコードの出力はどれですか?def f(*args):
return sum(args)
print(f(1, 2, 3, 4))
関数の可変長引数 *args / **kwargs と複数の戻り値
Python の可変長引数 args / *kwargs と複数の戻り値を図解で解説します。アンパックを含めた基本パターンまで一通り押さえます。
前回の def では、引数の数をあらかじめ決めて関数を定義しました。実際の処理では、いくつ引数が渡されるか事前には決まらない場合や、1 回の呼び出しで複数の結果を返したい場合が出てきます。
これらはそれぞれ 可変長引数 と 複数の戻り値 という仕組みで表現できます。どちらもタプルと辞書の知識が必要です。
可変長の位置引数 *args — タプルでまとめて受け取る
引数名の前にアスタリスクを 1 つ付けて *args とすると、位置引数を何個でもまとめて受け取れるようになります。関数の中では、受け取った引数はタプルとして扱えます。
名前は慣習で args がよく使われますが、技術的には *prices のように用途が読める名前でよいです。
# いくつでも位置引数を受け取れる
def sum_all(*prices):
print(prices) # (100, 200, 300)
print(type(prices)) # <class 'tuple'>
return sum(prices)
print(sum_all(100, 200, 300)) # 600
print(sum_all(500)) # 500(1 個でも OK)
print(sum_all()) # 0(0 個なら空タプル)
通常の引数と*argsを組み合わせる
*args の前に通常の引数を置けば、先頭の決まった引数と後ろの可変長部分を一緒に扱えます。log(level, *messages) のように書くと、最初の値は level に、残りはすべて messages というタプルにまとめられます。順序は通常の引数 → *args の順で書きます。
def log(level, *messages):
for m in messages:
print(f"[{level}] {m}")
log("INFO", "起動しました", "接続成功")
# [INFO] 起動しました
# [INFO] 接続成功
可変長のキーワード引数 **kwargs — 辞書でまとめて受け取る
アスタリスクを 2 つ付けて **kwargs とすると、**名前=値` の形で渡されたキーワード引数をまとめて受け取れます。関数の中では辞書として扱えるので、キー名が事前に決まっていない値**の受け渡しに使えます。
名前は慣習で kwargs(keyword arguments の略)がよく使われます。
show(name="田中", age=30) のように渡すと、受け取る側では {"name": "田中", "age": 30} という辞書になります。キー名を事前に決めずに受け取れるのが特徴です。
def describe_user(**info):
print(info) # {'name': '田中', 'age': 30, 'city': '東京'}
print(type(info)) # <class 'dict'>
for key, value in info.items():
print(f"{key}: {value}")
describe_user(name="田中", age=30, city="東京")
通常の引数と **kwargs を組み合わせる
通常の引数のあとに **kwargs を置けば、**必須の値はキーワードを固定**しつつ、**それ以外の任意項目は柔軟に受け取れる**書き方になります。def save_user(name, **info): のように書くと、name だけは必ず受け取り、追加の age=30 や city="東京" などはすべて info という辞書にまとめられます。
def save_user(name, **info):
print(f"name: {name}")
for key, value in info.items():
print(f"{key}: {value}")
save_user("田中", age=30, city="東京")
# name: 田中
# age: 30
# city: 東京
通常引数, *args, **kwargsを同時に使う
3 種類の引数は同時に書けます。順序は 通常の引数 → *args → **kwargs で固定です。def register(user_id, *tags, **meta): のように書くと、最初の値は user_id、それ以降の位置引数はすべて tags というタプル、key=value 形式の引数はすべて meta という辞書に分かれて入ります。
def register(user_id, *tags, **meta):
print(f"id: {user_id}")
print(f"tags: {tags}")
print(f"meta: {meta}")
register(1001, "admin", "beta", email="taro@example.com", active=True)
# id: 1001
# tags: ('admin', 'beta')
# meta: {'email': 'taro@example.com', 'active': True}
呼び出し側のアンパック — *リスト / **辞書
すでに作られたリストや辞書を関数に渡すときは、* や ` を付けるかどうか**で結果が大きく変わります。
* や ** を付けずにそのまま渡すと、リストや辞書は**展開されないまま 1 個の値**として扱われます。*args で受けると、中身がバラけずに「リストが 1 個入ったタプル」(例: ([1, 2, 3],)`)になってしまうのがポイントです。
呼び出し側で `リスト / 辞書 と書くと、リストや辞書の中身が 1 つずつバラけて渡るので、*args / **kwargs 側では期待どおり (1, 2, 3) というタプルや {"name": "花子"} という辞書を受け取れます。
def sum_all(*prices):
return sum(prices)
prices = [120, 280, 550]
# × リスト 1 個として渡される
# print(sum_all(prices)) # エラー(sum が中身とリスト自体を足そうとする)
# ○ `*` でバラす
print(sum_all(*prices)) # 950 — sum_all(120, 280, 550) と等価
# 辞書も同じ
def announce(name, city):
print(f"{name} さん({city})")
user = {"name": "花子", "city": "札幌"}
announce(**user) # announce(name="花子", city="札幌") と等価
アンパックは既存データの受け渡しで特に便利
すでにリストや辞書として手に入っているデータを関数に渡したいときは、アンパックを使えば 1 行で済みます。
たとえば辞書 user = {"name": "花子", "city": "札幌"} を announce(**user) と書くだけで、announce(name="花子", city="札幌") と同じ意味になります。キーをひとつひとつ取り出して書き直す必要がありません。
複数の戻り値 — タプルで返してアンパックで受け取る
return にはカンマで区切って複数の値を書けます。呼び出し元にはそれらを並べたタプルが返ってきます。受け取るときは a, b = 関数() と書けば、タプルをアンパックして複数の変数に一度に代入できます。
関数から複数の値を返すときに便利です(最小値と最大値、ユーザー名と権限、成功フラグとメッセージ など)。
# 複数の値をまとめて返す
def get_person():
return "田中", 30, "東京"
# タプルのまま受け取る
result = get_person()
print(result) # ('田中', 30, '東京')
print(type(result)) # <class 'tuple'>
# アンパックして個別の変数に
name, age, city = get_person()
print(name, age, city) # 田中 30 東京
# 実用例: 最小値と最大値を同時に返す
def get_min_max(values):
return min(values), max(values)
lowest, highest = get_min_max([120, 500, 300, 180])
print(f"最安 {lowest} 円 / 最高 {highest} 円")
残りをまとめたい場合は *残り を変数名の前に付ける
テストの点数のリスト return 90, 85, 78, 92, 88 を first, *middle, last = ... のように受け取ると、first=90、last=88、middle=[85, 78, 92] のように先頭・末尾だけ取り出して残りをリストにまとめることができます。「最初と最後だけ使いたい」「残りはまとめて保持したい」ときの書き方です。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2def f(**kwargs): で受け取った kwargs の型はどれですか?
Q3リスト nums = [1, 2, 3] の中身を、関数 f(a, b, c) に 3 つの位置引数として渡す書き方はどれですか?