はじめに
「みんな、ウタだよ!」、nikkieです。
PyCon JP 2022が近づいてきました。
私は10/14(金) 13:50〜「Pythonとアスタリスク 🐍🌟💫🐍🌟💫」というトークをします。
発表準備の中で気になったトピックをブログにアウトプットしちゃいます!
目次
- はじめに
- 目次
- 『Effective Python 第2版』項目23 キーワード引数にオプションの振る舞いを与える
- Python用語集より
- 項目23 キーワード引数にオプションの振る舞いを与える の納得感
- 終わりに
『Effective Python 第2版』項目23 キーワード引数にオプションの振る舞いを与える
この項目の主張としては納得感があります。
ただ読んでいく中で論の展開にひっかかるところが2つありました。
キーワード引数の利点を3つ論じていきます。
第1の利点は、キーワード引数で関数呼び出しを初めて読む人にとってわかりやすくなる
nikkie「たしかに🦀」。
名前付き引数(キーワード引数)が分かりやすいというのは『リーダブルコード』でも取り上げられています1。
キーワード引数の第2の利点は、関数定義においてデフォルト値を持てるという点です
nikkie「たし・・・あれ?」
ここが1つ目のひっかかりポイントです。
反例を挙げると、デフォルト値を持つ引数に位置引数で値を渡す こともできます!
例えば、Pythonチュートリアル 4.8.1. デフォルトの引数値では、デフォルト値を持つ関数 ask_ok
の呼び出し方を以下のように紹介しています。
def ask_ok(prompt, retries=4, reminder='Please try again!'): # 略
この関数はいくつかの方法で呼び出せます:
- 必須の引数のみ与える:
ask_ok('Do you really want to quit?')
- 一つのオプション引数を与える:
ask_ok('OK to overwrite the file?', 2)
- 全ての引数を与える:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
デフォルト値を持つ引数 retries
と reminder
にキーワード引数で値を渡していません。
利点の3つ目に進みましょう。
キーワード引数を使う第3の利点は、既存の呼び出し元と後方互換性を保ちながら、関数の引数を拡張できる強力な方法を提供するということです
nikkie「・・・あれ?」
これはキーワード引数の話というよりは、デフォルト値を持つ引数の話と考えます。
ひっかかりまとめ
- 「項目23 キーワード引数にオプションの振る舞いを与える」という主張には納得感
- 結論までの運びにひっかかる。特にキーワード引数の3つの利点
- 「キーワード引数が関数定義においてデフォルト値を持てる(利点2)」わけではない
- デフォルト値を持たせた引数には、位置引数でも キーワード引数でも値を渡せる
- 「キーワード引数が後方互換性を保ちながら、関数の引数を拡張できる方法(利点3)」ではない
- キーワード引数ではなく、デフォルト値を持たせた引数 ではないか
このひっかかりに対してPythonの用語集を引くと、私の中では整理が付いてスッキリしました。
『Effective Python 第2版』では仮引数と実引数が整理されずに論じられているように思われます。
用語集を読んでの理解を以下にまとめます。
Python用語集より
仮引数と実引数
- 仮引数 parameter
- https://docs.python.org/ja/3/glossary.html#term-parameter
- 関数の 定義で使う 引数のことですね
- 実引数 argument2
- https://docs.python.org/ja/3/glossary.html#term-argument
- 関数呼び出しで 渡す値 ですね
プログラミングFAQにも「実引数と仮引数の違いは何ですか?」という質問と回答があります。
このFAQが一番易しい説明と感じました。
「キーワード引数」「位置引数」は実引数の種類
用語集では、実引数には2種類あると続きます。
https://docs.python.org/ja/3/glossary.html#term-argument
- キーワード引数
- 「関数呼び出しの際に引数の前に識別子がついたもの (例:
name=
)」
- 「関数呼び出しの際に引数の前に識別子がついたもの (例:
- 位置引数
- 「キーワード引数以外の引数」
- 「位置引数は引数リストの先頭に書くことができ」
「キーワード引数」「位置引数」というとき、それは実引数(関数呼び出しでの値の渡し方)を指していたわけです!
- キーワード引数:名前付きで値を渡す
- 位置引数:(名前付きではなく)位置で値を渡す
これを確認して、『Effective Python 第2版』項目23のキーワード引数の利点2へのひっかかりは明確に言語化されました。
キーワード引数(実引数)の利点としながら、「関数定義においてデフォルト値を持てる」と仮引数の話をしているのです。
仮引数の種類
仮引数の用語集を見てみると、なんと5種類あると続きます。
https://docs.python.org/ja/3/glossary.html#term-parameter
この中の「位置またはキーワード」に注目します3。
位置 であるいは キーワード引数 として渡すことができる引数を指定します。
これは(略)、デフォルトの仮引数の種類です:
関数定義に使う引数(仮引数)はデフォルトで 位置またはキーワード引数 となります。
位置またはキーワードの仮引数に値を渡す(※実引数)には、位置引数もキーワード引数も使えます。
仮引数の別の種類
仮引数の用語集には、別軸の種類の記載もあります。
https://docs.python.org/ja/3/glossary.html#term-parameter
仮引数はオプションと必須の引数のどちらも指定でき、オプションの引数にはデフォルト値も指定できます。
デフォルト値の指定は オプション引数か必須引数か に関わります。
これは「位置またはキーワード」など5種類とは別の種類と認識しました。
用語集に例として挙がっている以下のfunc
について
def func(foo, bar=None): ...
foo
は(デフォルト値を持たないので)必須引数foo
は位置引数でもキーワード引数でも指定できる
bar
はデフォルト値を持つのでオプション引数bar
は位置引数でもキーワード引数でも指定できるfoo
もbar
も同様に「位置またはキーワード」の仮引数
位置またはキーワード引数(=「位置またはキーワード」という種類の仮引数)についてまとめると
デフォルト値/実引数 | 位置引数 | キーワード引数 |
---|---|---|
必須 | ○ | ○ |
オプション | ○ | ○ |
関数の引数を拡張するのは、オプション引数
ここまで見てくると、関数があって、後方互換性を保ちながら関数の引数を拡張する方法は、デフォルト値を持たせた引数(=オプション引数)と分かります。
引数を追加する際にデフォルト値を持たない必須引数とすると、それを指定するために既存の呼び出しをすべて修正しなければいけませんね。
それに対してオプション引数であれば、既存の呼び出しは修正不要です(デフォルト値が使われますからね)。
項目23 キーワード引数にオプションの振る舞いを与える の納得感
用語集を元に、『Effective Python 第2版』の論の運びには、仮引数と実引数の混在があることを見てきました。
ですが、項目のタイトルには納得感があります。
最後にこの点を考えてみます。
デフォルト値を持ったオプションの位置またはキーワード引数には、位置引数でもキーワード引数でも値を渡せます。
オプションの引数が増えた時を考えてみましょう。
def f(a, b, x="spam", y="ham", z="egg"): ...
関数f
の仮引数について
- 必須引数:
a
,b
- オプション引数:
x
,y
,z
です。 いずれも位置またはキーワード引数です。
ここでy
だけデフォルトではない値を与えることを考えます。
y
に位置引数で渡そうとすると
f(101, 23, "spam", "beef")
のように、x
のデフォルト値も渡した上でy
に値を渡すことになります。
f(101, 23, "beef")
ではx
に位置引数で"beef"
を渡したことになりy
には渡せていません。
y
を位置引数で渡すためには、1つ前のx
も位置引数で渡す必要があります(実引数は位置引数➡️キーワード引数の順)。
この場合y
はキーワード引数で渡すとスッキリします。
f(101, 23, y="beef")
x
の指定がなくなりました!
位置引数と比べて、わかりやすさが段違いだと感じます。
これが「項目23 キーワード引数にオプションの振る舞いを与える」の言いたいこと(感じ取った納得感)なのだと思います。
気になる点としては、キーワード引数は実引数についての言葉で、オプションの振る舞いは仮引数に言及しています。
言わんとしているところをなるべく正確に表してみると、
「オプションの仮引数にはキーワード引数で値を渡す」
といった風になるのではないでしょうか。
終わりに
『Effective Python 第2版』項目23で感じたひっかかりを元に、ドキュメントの用語集を紐解いたところ、関数の引数について整理されました。
- 位置引数・キーワード引数は実引数のことを言っている
- 位置またはキーワード引数は仮引数のことを言っている
- 仮引数には別軸の種類としてデフォルト値の指定による「必須/オプション」がある
Pythonの関数の(実)引数は位置引数もキーワード引数もあるというのは入門時から知っていましたが、この自由度の高さとは裏腹に引数についてはやや複雑になっているように見受けられます。
ただそんな用語たちもドキュメントを通して整理されました。
この記事の内容に不正確な点があるなどお気づきの点あれば、@ftnextまでお知らせください。
このフィードバックが『Effective Python 第2版』の著者に届くかは分かりませんが、関数の引数、めっちゃ勉強になりました!
項目23はひっかかる内容ではありましたが、他の項目はどれも非常に学びがあり、書いてくれてありがとうという気持ちですし、第3版で修正されたらいいなと願っています。