やりたいこと
下記のような、スパムとハムを任意の数、(対象物へ)食べさせる関数があるとします。
def feed(spam: int = 0, ham: int = 0) -> None:
...
この関数は対象物にスパムとハムを食べさせるときに便利なので、チーム内で広く可読性よくコーディングさせたいです。
何を何個食べさせるか可読性良く明示することを強制させるとしたら、このような実装になるでしょう。
def feed(*, spam: int = 0, ham: int = 0) -> None:
...
# できる
feed(spam=1)
feed(ham=2)
feed(spam=1, ham=2)
feed(ham=2, spam=1)
# できない
feed(1)
feed(1, 2)
しかし、チーム内のドメイン知識として、いつも同時にスパムとハムを食べさせるときには必ず(spam, ham)
の順番にすることが浸透していました。
そのため、2つ同時に与えるときに引数名が必ず必要なのは開発体験が低くなってしまいます。
-
feed(ham=2, spam=1)
となっている分には誤解がないので許容されています。
しかし、feed(1)
だとスパムだけを1つ食べさせるのかハムだけを1つ食べさせるのかわからないので、1種類だけ食べさせるときにはfeed(spam=1)
, feed(ham=2)
のように名前付き引数を使うことが推奨されています。
また、どれも1つも食べさせない時にはそもそも関数を呼ばないことが推奨されています。
# 推奨
feed(spam=1, ham=2)
feed(ham=2, spam=1)
feed(1, 2)
feed(spam=1)
feed(ham=2)
# 非推奨
feed(1, ham=2)
feed(1)
feed()
このようなことをコーディング規約として守らせたいとき、残念ながらPython 3.11現在では(上述のキーワード限定引数のようなことを)実行時レベルで簡単にやる方法はありません。
引数指定の違反をtyping.overload
を使ってコーダーに伝える
しかし、typing.overload
を使うと、非推奨の使い方をしている場合にはVSCodeの言語サーバーpylance
(が内蔵する静的型チェッカーpyright
)に警告を出させることが可能です。
from typing import overload
@overload # 引数名を両方とも指定しない
def feed(spam: int, ham: int, /) -> None: ...
@overload # 引数名を両方とも指定する
def feed(*, spam: int, ham: int) -> None: ...
@overload # 引数名をspamだけ指定する
def feed(*, spam: int) -> None: ...
@overload # 引数名をhamだけ指定する
def feed(*, ham: int) -> None: ...
# 実装
def feed(spam: int = 0, ham: int = 0) -> None:
...
# 実行時にはいずれもエラーは発生しない
feed(spam=1, ham=2)
feed(ham=2, spam=1)
feed(spam=1)
feed(ham=2)
feed(1, 2)
# 非推奨
feed(1, ham=2)
feed(1)
feed()
あくまでも型チェッカーによる静的解析の結果の警告なので、実行時にはエラーなく動きます。
しかし目的はコーディング規約を守らせることなので、これはこれで目的を達成することができました。
参考文献
https://docs.python.org/ja/3.10/glossary.html - "parameter"
https://docs.python.org/ja/dev/library/typing.html#typing.overload
https://peps.python.org/pep-0484/