[Pythonクイズ]その引数、どのパラメーターに渡されるのか、分かりますか?:Pythonステップアップクイズ
Pythonでは関数のパラメーターには幾つかの種類があり、引数の渡し方にも位置引数やキーワード引数といった種類があります。どのパラメーターにどの引数が渡されるのか、ちゃんと理解しているかをクイズで確認してみましょう。
【問題】
以下は幾つかの引数を受け取る関数funcの定義と、それを呼び出すコードである。以下のように関数funcを呼び出したとき、パラメーターcの値は何になるか、選択肢の中から適切なものを選択せよ。
def func(a, /, b, *args, c):
print(f'{a=}, {b=}, {args=}, {c=}')
func(1, 2, 3, 4, 5, 6)
【選択肢】
- 6
- (4, 5, 6)
- (5, 6)
- 例外が発生する
というわけで、大好評(?)のPythonクイズ、第2回のお時間がやってまいりました。どうも、「へなちょこPythonistaかわさき」略して「HPかわさき」です。
週刊少年マンガ雑誌の新連載の第2回って、ホントに読者からの評判がよくってキャッチコピーに「大好評!」「人気沸騰!」とか付けてんの? と昔から謎に思っていました。もっとも、今では紙媒体でもSNSやインターネット上の掲示板で読者の反応を調べられるので、以前よりはこうしたキャッチコピーに対する疑問は薄らいできました(とはいえ、印刷に入るタイミングもあるしなぁ。ホントなのかなぁ)。
本フォーラム「Deep Insider」はWebメディアなので、記事のページビュー(PV)をリアルタイムにチェックできます。なので、第1回を多くの方に読んでいただけたかどうかくらいは分かるんです。その結果、「大好評(?)」くらいは書いていいんじゃないかなと今のところは考えています(笑)。
と、ヨタ話をここに書いているのは、問題文と答えが近過ぎるのはよくないんじゃないかと考えているからです。次回以降もここに毎回埋め草が入りますので、クイズと同じくらい、埋め草にどんなネタをひねり出してくるのかを楽しみにしてください(埋め草のためだけにHPかわさきというキャラが作られたといっても過言ではないのです)。
では、正解発表のお時間です。
【答え】
正解は「4. 例外が発生する」でした。
その理由を簡単にまとめると、cはキーワード専用パラメーターですが、関数funcの呼び出し時に「c=……」のようにしてキーワード引数を関数に与えていないからです。
Windowsの対話環境で試したところを以下に示します。
エラーメッセージを読むと確かに「関数funcには1つのキーワード専用引数が必要だけど、それがありません」といった内容のテキストが書かれていますね。詳しい説明はこの後で。
【解説】
Pythonの関数(メソッド)を呼び出すときには、次の2つの方法で引数を渡せます。
- 位置引数:関数名に続けて指定した引数が、そのままの順番で関数のパラメーターに渡される
- キーワード引数:どのパラメーターにどの引数を渡すかをキーワード(パラメーター名)で指定する
これらを混在させることも可能ですが、その場合には位置引数を先に書く必要があります。例えば、以下のようなint関数の呼び出しはOKです。
n = int('11', base=16)
しかし、以下の書き方はできません。
n = int(base=16, '11')
一方、関数を定義する際にパラメーターリストには、次のような種類のパラメーターを記述できます。
- 位置専用パラメーター:位置引数しか受け取れない。位置専用パラメーターはパラメーターリストの先頭に置き、他の種類のパラメーターとは'/'で区切る
- 「位置またはキーワード」パラメーター:位置引数を受け取ることも、キーワード引数を受け取ることも可能。可変長位置パラメーターよりも前に置く
- 可変長位置パラメーター:任意の数の位置引数を受け取り、それらをタプルに格納する。一般的には「args」という名前を持ち、パラメーターリストではその前に「*」を付けて「*args」のように記述する
- キーワード専用パラメーター:キーワード引数しか受け取れない。キーワード専用パラメーターはパラメーターリスト中の'*'よりも後、可変長キーワードパラメーター(**kwargs)よりも前に置く
- 可変長キーワードパラメーター:任意の数のキーワード引数を受け取り、それらを辞書に格納する。一般的には「kwargs」という名前を持ち、パラメーターリストではその前に「**」を付けて「**kwargs」のように記述する。可変長キーワードパラメーターはパラメーターリストの最後に置く。
上の説明ではキーワード専用パラメーターの位置について「パラメーターリスト中の'*'よりも後」と書きましたが、これは単独の'*'だけではありません。可変長位置パラメーターは「*args」のように書きますが、この「*」も対象となることに注意してください。
図にすると、こんな感じです。
また、'/'よりも後、'*'よりも前にあるパラメーターは「位置またはキーワード」パラメーターとなります。パラメーターリストに'/'も'*'もなければ、全てのパラメーターが「位置またはキーワード」パラメーターです。
では、以上を踏まえて、問題文の関数定義を見てみましょう。
def func(a, /, b, *args, c):
print(f'{a=}, {b=}, {args=}, {c=}')
それぞれのパラメーターが何であるかを考えると次のようになります。
- a:'/'より前にあるので位置専用パラメーター
- b:'/'より後で、*argsより前なので「位置またはキーワード」パラメーター
- args:可変長位置パラメーター
- c:*argsよりも後にあるのでキーワード専用パラメーター
そして、関数呼び出しは次のようになっていました。
func(1, 2, 3, 4, 5, 6)
引数がどのパラメーターに渡されるかは次の通りです。
- 1:位置引数として位置専用パラメーターのaに渡される
- 2:位置引数として「位置またはキーワード」パラメーターのbに渡される
- 3、4、5、6:(可変長の)位置引数として可変長位置パラメーターのargsに渡される
このように、パラメーターcに渡される値がありません。選択肢1の「6」のように、可変長位置パラメーターのargsに「(3, 4, 5)」が渡され、cに残りの「6」が渡されるような都合が良いことは起きません。選択肢の2と3についても同様です。Pythonは単純にパラメーターの種類に応じて、どの引数をどのパラメーターに渡すかを判断し、整合性が取れなければ例外を発生します。そして、ここではつじつまが合っていないのでTypeError例外が発生したというわけです。
ではパラメーターcに値をちゃんと渡すにはどうしたらよいでしょう。これにはキーワード引数を使って、例えば、次のように関数funcを呼び出します。
def func(a, /, b, *args, c):
print(f'{a=}, {b=}, {args=}, {c=}')
func(1, 2, 3, 4, 5, c=6) # a=1, b=2, args=(3, 4, 5), c=6
Pythonの関数や引数などについては「Python入門」の以下の記事が参考になります。
- 「関数の基本」
- 「関数の引数」
- 「関数のローカル変数とスコープ」
- 「ローカル関数とラムダ式」
興味のある方は、ぜひ読んでくださいね。
Copyright© Digital Advantage Corp. All Rights Reserved.