関数の実引数 — 名前、デフォルト、アスタリスク
実引数を名前で渡すこと、デフォルト値とその順序ルール、*args と **kwargs、リストや辞書を呼び出し時にアンパックすること。
どうやって学びますか?
解決する問題
前章の関数は、1通りの呼び方しかできませんでした:仮引数と同じ数の実引数を、ちょうどその順番で渡す。
実際にはそれでは足りません。注文を扱う関数を考えてみましょう:
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 32行目は何もエラーを出しませんでした — ただ意味のない答えを返しただけです。Python は実引数の型をチェックしません:ここでは文字列と数字が入れ替わりましたが、f-string はどちらも表示できるため、何も防げませんでした。Python には順序が逆だったことを知る術がありません。
それに、ほとんどの注文が 1 個なら? 毎回 1 を書くことになります。関数に 4 つのオプション設定があるなら? 変えないものまで含めて、毎回 4 つすべてを渡すことになります。
この章では、実引数を渡す残りの方法 — 名前渡し、デフォルト値、任意の個数 — を扱います。
この章を終える頃には
- 実引数を名前で渡せ、いつそうすべきか判断できる
- デフォルト値を与え、その順序ルールを守れる
- キーワード専用仮引数を作り、名前でしか渡せないようにできる
*argsで任意個数の実引数を受け取れる**kwargsで任意の名前の実引数を受け取れる- リストや辞書を呼び出し時にアンパックできる
前提条件: 関数 — 1つの判断、1つの名前.
名前で渡す
順序を覚える代わりに、名前を書きます:
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x pen名前があれば順序は関係ありません — quantity を先に書いても、正しい場所に入ります。
いつ使うか? 実引数だけでは意味が通じないときです。order("pen", 3) なら読めます。しかし send(True, False) では何も伝わりませんし、send(retry=True, verbose=False) なら伝わります。ルールとして:True/False や裸の数字を渡すときは名前を付けましょう。
デフォルト値
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))1 x pen
5 x pen
5 x penquantity=1 は「誰も指定しなければ 1 とみなす」と言います。よくあるケースが短く書け、珍しいケースもまだ表現できます。
守らなければならないルールが 1 つあります:デフォルト付き仮引数は必ず後ろに置く。
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a default理由は考えれば明らかです:order("pen") が与えられたとき、Python が "pen" をどっちの仮引数に入れるか分かりません。必須のものを先に置けば、曖昧さは生じません。
任意個数の実引数 — *args
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0*prices のアスタリスクは「残りの位置引数をここに集める」と言います。いくつ来るか事前に決める必要がなく、ゼロ個でも構いません。
集められたものは何でしょう?
def show(*things):
print(type(things))
print(things)
show(1, "two")<class 'tuple'>
(1, 'two')タプルです — 第14章のタプル。理由はすっきりしています:いくつ実引数が来るかは呼び出しの瞬間に決まり、後から変わることはないため、イミュータブルな容器が適しています。
pricesという名前は必須ではありません。 慣習的な名前は*argsで、ほとんどのコードでこれを見ることになります。しかし意味のある名前があるならそれを使う方が良いです —*pricesは何が来るかを読み手に伝えますが、*argsは伝えません。
任意の名前の実引数 — **kwargs
def describe(**fields):
print(type(fields))
for key, value in fields.items():
print(f"{key}: {value}")
describe(item="pen", quantity=3)<class 'dict'>
item: pen
quantity: 3アスタリスク 2 つで、名前付きで渡された実引数を集め、結果は辞書 — 名前と値の組み合わせ — になり、まさにあるべき形です。
アスタリスク 1 つならタプル、2 つなら辞書。この組み合わせを覚えれば、あとは自然に続きます。
名前でしか渡せない仮引数
*args の後ろにある仮引数は、位置で渡せません。なぜなら *args が残りの位置引数をすべて自分で受け取ってしまうからです。だから名前でしか渡せません。これをキーワード専用仮引数と呼びます:
def total(*prices, discount=0.0):
return sum(prices) * (1 - discount)
print(total(100, 50))
print(total(100, 50, discount=0.1))
print(total(100, 50, 0.1))150.0
135.0
150.13行目を見てください。0.1 は discount になりませんでした — prices にさらに 1 つの値として入ってしまい、エラーもなく間違った答えが返ってきます。discount を渡すには discount=0.1 と書く必要があります。
*args を使いたくない場合でも、仮引数リストに裸の * を置けば、それ以降をキーワード専用にできます:
def send(message, *, retry=False):
return f"{message} (retry={retry})"
print(send("hi", retry=True))
print(send("hi", True))hi (retry=True)
TypeError: send() takes 1 positional argument but 2 were given(2行目の前にもトレースバックが表示されますが、ここでは最後の行だけ示しています。)
これは True/False 設定にとても便利です:呼び出し側が retry=True と書かざるを得ないため、呼び出しを読めばその True が何のためか一目で分かります。
順序は決まっている
すべてを並べると、完全なルールはこうなります:
- 普通の仮引数を先に。その中では、デフォルトなしのものがデフォルトありのものより前。
- 次に
*args(または裸の*)。 - 次にキーワード専用仮引数 — デフォルトの有無と順序は自由。
- 最後に
**kwargs。
def f(a, b=1, *args, c, d=2, **kwargs):
print(a, b, args, c, d, kwargs)
f(10, c=3)
f(10, 20, 30, 40, c=3, e=5)10 1 () 3 2 {}
10 20 (30, 40) 3 2 {'e': 5}c はデフォルトがないのに b=1 (デフォルトあり) より後ろに来ていますが、これは合法です — *args より後ろはすべて名前で来るため、それぞれの値がどこへ行くか曖昧さがないからです。「デフォルトは後ろ」ルールが適用されるのは *args より前の部分だけです。
最初は面倒に見えますが、理由は単純です:Python はどの実引数がどの仮引数へ行くか知る必要があり、*args が「残り全部」を取る以上、そのあとに位置仮引数を置けないのです。
呼び出し時のアンパック
アスタリスクは呼び出し側でも使え、そこでは逆の働きをします:
def order(item, quantity):
return f"{quantity} x {item}"
values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}
print(order(*values))
print(order(**fields))3 x pen
2 x bag*values はリストをアンパックします — order(values[0], values[1]) と書いたのと同じです。**fields は辞書をアンパックし、そのキーは仮引数名と一致しなければなりません。
定義でのアスタリスクは集める;呼び出しでのアスタリスクはアンパックする — 同じ記号、逆の方向。
完全な例
invoice.py:
# Arguments, in the order Python expects them
def invoice(customer, *items, currency="unit", discount=0.0, **extra):
total = sum(price for _, price in items)
after = total * (1 - discount)
lines = [f"Invoice for {customer}"]
for name, price in items:
lines.append(f" {name:<10} {price:>8.2f} {currency}")
lines.append(f" {'subtotal':<10} {total:>8.2f} {currency}")
if discount:
lines.append(f" {'discount':<10} {discount * 100:>7.0f}%")
lines.append(f" {'total':<10} {after:>8.2f} {currency}")
for key, value in extra.items():
lines.append(f" note {key}: {value}")
return "\n".join(lines)
print(invoice("rafi", ("pen", 15.0), ("bag", 850.0)))
print()
print(invoice("dia", ("ink", 120.0), discount=0.1, reference="A-77"))Invoice for rafi
pen 15.00 unit
bag 850.00 unit
subtotal 865.00 unit
total 865.00 unit
Invoice for dia
ink 120.00 unit
subtotal 120.00 unit
discount 10%
total 108.00 unit
note reference: A-77気づく価値のある 4 点。
customer は必須で、それ以外はすべてオプション。 顧客なしの請求書は意味がないため、普通の仮引数にします。品目がゼロの場合もあるため *items とします。
discount=0.0 はデフォルトであるだけでなく、条件を単純化します。 if discount: 行は第8章の真理値ルールを使います — 0.0 は偽なので、割引がないときその行は決して表示されません。そして discount は *items より後ろにあるためキーワード専用 — discount=0.1 のように渡さなければなりません。
`extra は関数が知らないものを吸収します。** reference="A-77" はどの仮引数にも合致しないため extra` に入り、備考として表示されます。こうして誰も想定していなかったフィールドを関数が受け入れられるのです。
for _, price in items は第14章のアンパックで、アンダースコアが「ここは名前が不要」と言います。items はタプルのタプルで、内側のタプルがそれぞれアンパックされます。
うまくいかないとき
SyntaxError: parameter without a default follows parameter with a default デフォルト付き仮引数が必須のものより前に置かれました。必須を先に、デフォルトを後ろに。
TypeError: order() takes 2 positional arguments but 3 were given 実引数が多すぎます。メッセージに「いくつ受け取るか」と「いくつ来たか」の両方が出ます。
TypeError: order() got an unexpected keyword argument 'qty' 名前で渡したが、その名前の仮引数がありません。スペルを確認してください — **kwargs を持つ関数なら文句を言わず、静かに吸収していたでしょう。
TypeError: order() got multiple values for argument 'item' 同じ仮引数が 2 回与えられました — 1 回は位置で、1 回は名前で (order("pen", item="bag"))。
関数は動くが答えが逆 実引数が逆順だったのにエラーが出ませんでした — 関数の中身がどちらの値でも動くからです。名前で渡しましょう — この章の冒頭で見た静かなミスです。
*` でアンパックしたが個数が合わない** order(*values) で values が関数の仮引数とちょうど同じ数でなければなりません。print(len(values))` で確認しましょう。
ステップ 4 / 6 — 予想する
理解度をチェック
quantity にデフォルトがあります。2行は何を表示するでしょう?
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))- A1 x pen 5 x pen
- B1 x pen 1 x pen
- CA `TypeError`
- Dpen x 1 pen x 5
*things は何という種類のものを集めますか?
def show(*things):
print(type(things))
print(len(things))
show(1, "two", 3.0)- A<class 'tuple'> 3
- B<class 'tuple'> 1
- C<class 'list'> 3
- D<class 'dict'> 3
デフォルト付き仮引数が先に置かれました。どうなりますか?
def order(quantity=1, item):
return item- A普通に動く
- B動くが、`order("pen")` を呼ぶと失敗する
- CA `SyntaxError` — デフォルト付き仮引数を必須のものより前に置けない
- DA `TypeError`
回答にはアカウントが必要です
ログインして答えを確認
問題は上にあります。頭の中で答えを考えることがいちばん大切です。ログインすると、正解・解説・3 段階のヒントを確認できます。
やってみよう
message.py というファイルを作り、次の関数を 1 つ書きましょう:
def notify(recipient, *lines, subject="Notice", urgent=False, **details)この関数は表示ではなく、読みやすいメッセージを返す ものにします — 宛先、件名、各行を別々の行に、details の各組み合わせを下部に備考として。urgent が True なら、件名の前に何かを付け加えます。
それから 4 通りで呼び出します:
- 宛先だけで、行はなし
- 3 行で、それ以外はすべてデフォルト
urgent=Trueとsubjectを、両方名前で- リストと辞書を作り、
*と**でアンパックして渡す
それからわざと 3 回壊し、それぞれ何が起きるか確認します:
subject="Notice"仮引数をrecipientより前に移動する。ファイルを実行する前からSyntaxError: parameter without a default follows parameter with a defaultが出ます。notify("rafi", subjekt="hi")と書き、スペルを間違える。エラーは出ません:**detailsが間違った名前も静かに受け入れるため、subjektが備考になり、件名は"Notice"のままです。返されたメッセージを表示して、自分の目で確認してください。notify("rafi", recipient="dia")と書く。TypeError: notify() got multiple values for argument 'recipient'が出ます。
Python はこのうち 2 つを即座に捕まえ、1 つを見逃します — そして見逃したほうが最も危険です。これが **kwargs の便利さの代償です:スペルを誰もチェックしなくなります。メッセージを一度覚えれば、あとで数秒で捕まえられますし、静かなミスがどこに潜むかも分かるようになります。