nikkie-ftnextの日記

イベントレポートや読書メモを発信

『Effective Python 第2版』項目23に感じたひっかかりを機に関数の引数を復習しました

はじめに

みんな、ウタだよ!」、nikkieです。

PyCon JP 2022が近づいてきました。
私は10/14(金) 13:50〜「Pythonとアスタリスク 🐍🌟💫🐍🌟💫」というトークをします。
発表準備の中で気になったトピックをブログにアウトプットしちゃいます!

目次

『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!')

デフォルト値を持つ引数 retriesreminder にキーワード引数で値を渡していません

利点の3つ目に進みましょう。

キーワード引数を使う第3の利点は、既存の呼び出し元と後方互換性を保ちながら、関数の引数を拡張できる強力な方法を提供するということです

nikkie「・・・あれ?」

これはキーワード引数の話というよりは、デフォルト値を持つ引数の話と考えます。

ひっかかりまとめ

  • 「項目23 キーワード引数にオプションの振る舞いを与える」という主張には納得感
  • 結論までの運びにひっかかる。特にキーワード引数の3つの利点
  • 「キーワード引数が関数定義においてデフォルト値を持てる(利点2)」わけではない
    • デフォルト値を持たせた引数には、位置引数でも キーワード引数でも値を渡せる
  • 「キーワード引数が後方互換性を保ちながら、関数の引数を拡張できる方法(利点3)」ではない
    • キーワード引数ではなく、デフォルト値を持たせた引数 ではないか

このひっかかりに対してPythonの用語集を引くと、私の中では整理が付いてスッキリしました。
『Effective Python 第2版』では仮引数と実引数が整理されずに論じられているように思われます。
用語集を読んでの理解を以下にまとめます。

Python用語集より

仮引数と実引数

プログラミング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位置引数でもキーワード引数でも指定できる
    • foobarも同様に「位置またはキーワード」の仮引数

位置またはキーワード引数(=「位置またはキーワード」という種類の仮引数)についてまとめると

デフォルト値実引数 位置引数 キーワード引数
必須
オプション

関数の引数を拡張するのは、オプション引数

ここまで見てくると、関数があって、後方互換性を保ちながら関数の引数を拡張する方法は、デフォルト値を持たせた引数(=オプション引数)と分かります。
引数を追加する際にデフォルト値を持たない必須引数とすると、それを指定するために既存の呼び出しをすべて修正しなければいけませんね。
それに対してオプション引数であれば、既存の呼び出しは修正不要です(デフォルト値が使われますからね)。

項目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版で修正されたらいいなと願っています。



  1. 「6.7 「名前付き引数」コメント」

  2. ドキュメントを通して、仮引数=parameter・実引数=argumentと用語が統一されているわけではなさそうです。例えばPythonチュートリアル4.8.3. 特殊なパラメータには仮引数(parameter)の種類の話ですが、argumentという語が登場しています

  3. 残りの4種類はアスタリスクが関わるので、トークで触れます