PythonのNoneが抱える曖昧さを、標準のsentinelがついに解決する
Much Ado About None: Sentinel Values in Python
Noneは「値がない」「未設定」「エラー」など多くの意味を兼ねてしまい、キャッシュや設定更新で誤解を生む。有効な値と決して衝突しないセンチネル値ならこの曖昧さを解消できる。長年保留されていたPEP 661が承認され、Python 3.15からsentinelが組み込み関数となり、型チェッカーもNoneと同様に絞り込めるようになった。古いバージョン向けにはtyping_extensionsのバックポートがある。
私の主な結論はこうだ。Noneの曖昧さを意識し、自分が本当に表現したいのは何なのかを考えること。曖昧さを解消するにはセンチネルを使い、ライブラリがすでに提供しているものを優先し、古いobject()やEnumのハックは新しい組み込みに置き換えよう。
HNでの議論
35- pwdisswordfishq
optionalモナドを導入するのを避けるのに、ずいぶん手間がかかるものだな。
- lpghatguy
Luaをたくさん書いていた頃、Noneセンチネル値があるのは大いに気に入っていた(Luaでは不在を表すのに'nil'を使う)。PydanticのMISSINGに似た多くの場面で役立った。
この区別はJavaScriptの実際に便利な点だと思うし、'null'と'undefined'を区別できるのは同じような理由で時に役立つ。
- RantyDave
「単純なキャッシュは、キーが存在しない場合も、キーがNoneにマップされている場合もNoneを返す」
いや、KeyErrorを返す。少なくとも返すべきだ。
- est
自分はカスタマイズしたException型を返すだけだ。ほぼ同じように機能する。
- throwaway2037
記事からのサンプルコードはこちら:
> MISSING = sentinel("MISSING")
> def update_email(user_id: int, email: str | None | MISSING = MISSING) -> None:
> if email is MISSING:
> return # 更新するものがない
> elif email is None:
> db.delete_email(user_id)
> else:
> db.set_email(user_id, email.lower()) # strに絞り込まれる
このスタイルは長年のPythonプログラマからよく見かける。言語が非常に動的なので、人々はこれが良いプログラミング方法だと思い始める。辛口の意見だが、そうではない。delete_email()とset_email()という別々の関数にしないのはなぜ?その方が明確だろう。