Python | テスト・設計・品質:Optional

Python Python
スポンサーリンク

Optionalって何?一言でいうと「Noneかもしれない」を型で表す道具

Optional は、型ヒントの世界で
「この値は、あるときは T 型だけど、ないときは None かもしれない」
という状態を表すための記号です。

もう少し形式的に言うと、

Optional[T] は Union[T, None] の別名
Python

です。
つまり、Optional[str] は「str または None」という意味になります。

Python 3.10 以降なら、str | None と書けますが、
意味は Optional[str] とまったく同じです。

ここでの超重要ポイントは、

「None になる可能性があることを、型として“明示する”」

というところです。
これが、設計・テスト・バグ防止のすべてに効いてきます。


まずはシンプルな例で「Optionalの感覚」をつかむ

ユーザー名を探す関数の例

ユーザーIDから名前を探す関数を考えます。

def find_user_name(user_id: int) -> str:
    ...
Python

こう書いてあると、「必ず str が返ってくる」と読めます。
でも、実際には「見つからないとき」がありますよね。

そのときに None を返す設計にするなら、
型ヒントもそれに合わせてこう書くべきです。

from typing import Optional

def find_user_name(user_id: int) -> Optional[str]:
    if user_id == 1:
        return "Taro"
    return None
Python

これで、

「この関数は、str が返るかもしれないし、None かもしれない」

という仕様が、コードとしてはっきり残ります。

Python 3.10 以降なら、こうも書けます。

def find_user_name(user_id: int) -> str | None:
    ...
Python

どちらでも意味は同じです。


Optionalがテストと品質に効いてくるポイント

呼び出し側に「Noneのときどうする?」を考えさせる

Optional[str] と書いてある関数を呼ぶ側は、
必ず「None の可能性」を意識することになります。

name = find_user_name(1)
print(name.upper())
Python

型ヒントがないと、こう書いてしまいがちです。
でも、Optional[str] だとエディタや静的解析ツールが
nameNone かもしれないよ」と警告してくれます。

それを受けて、呼び出し側はこう書き直します。

name = find_user_name(1)
if name is not None:
    print(name.upper())
else:
    print("ユーザーが見つかりませんでした")
Python

この「None のときどうする?」という分岐は、
本来は最初から設計に含めるべきものです。

Optional を付けることで、
その設計上の前提が「型」として強制されるイメージです。

テストケースの抜けに気づきやすくなる

Optional[str] と書いてある関数をテストするとき、
自然にこう考えるようになります。

見つかったとき(str が返るパターン)
見つからなかったとき(None が返るパターン)

つまり、「少なくとも 2 パターンはテストしよう」と思えるわけです。

型ヒントがないと、
「たまたま見つかるケースだけテストして終わり」
ということが起きやすくなります。

Optional は、「仕様上の分岐」を型で表現することで、
テストの観点を漏れにくくしてくれます。


Optionalを使うときに絶対に押さえておきたい注意点

「なんでもかんでもOptional」は逆効果

便利だからといって、
何でもかんでも Optional を付けるのは逆効果です。

例えば、こういうのはよくありません。

def add(a: Optional[int], b: Optional[int]) -> Optional[int]:
    if a is None or b is None:
        return None
    return a + b
Python

本当に None を受け取りたい関数ならいいのですが、
「呼び出し側がちゃんと int を渡すべき」関数なら、
素直にこう書くべきです。

def add(a: int, b: int) -> int:
    return a + b
Python

Optional を付けるということは、
「None を渡してもいいし、None を返してもいい」という契約を結ぶことです。

それはつまり、「呼び出し側の責任も増える」ということでもあります。

本当に必要なところだけに Optional を付ける。
「面倒だからとりあえず Optional」はやめる。
ここが品質的にはかなり重要です。

「Noneを返す設計」自体を見直すべき場面もある

例えば、ユーザーが見つからないときに None を返す代わりに、
例外を投げる設計にすることもあります。

class UserNotFoundError(Exception):
    ...

def get_user_name(user_id: int) -> str:
    if user_id == 1:
        return "Taro"
    raise UserNotFoundError
Python

この場合、戻り値は str だけでよく、
Optional は不要です。

どちらが正しいかは「仕様次第」ですが、
Optional を付ける前に、

「本当に None を返す設計が適切か?」

を一度立ち止まって考えるのが大事です。


OptionalとUnionの関係をもう少しだけきちんと理解する

Optional[T] は Union[T, None] のシンタックスシュガー

型レベルでは、

from typing import Optional, Union

Optional[str] は Union[str, None] と同じ
Python

です。

Python 3.10 以降なら、

str | None  # これも同じ意味
Python

と書けます。

つまり、Optional は「None を含む Union」を
読みやすく書くためのエイリアスです。

「None以外も混ざる」なら Optional ではなく Union を使う

例えば、「int か str か None」という型を表したいなら、
Optional ではなく Union を使います。

from typing import Union, Optional

value: Union[int, str, None]  # これが正しい
value: Optional[int | str]    # こう書くと意味が変わるので注意
Python

Optional[int | str]
int | strNone」=「intstrNone」なので、
結果的には同じ意味になりますが、
読みやすさ的には int | str | None の方が素直です。


初心者がOptionalとどう付き合うといいか

まずは「戻り値」から使い始める

いきなり引数にも Optional を付け始めると、
設計がごちゃごちゃになりがちです。

最初のステップとしては、

「見つからないときに None を返す関数」の戻り値にだけ Optional を付ける

くらいから始めるのがちょうどいいです。

例:検索系、find 系、get_or_none 系の関数など。

そこから少しずつ、

この引数は None を許すべきか?
この戻り値は本当に None を返すべきか?

といった設計の問いを増やしていくと、
Optional の使いどころの感覚が育っていきます。

「Noneかもしれない」を放置しない癖をつける

Optional を付けたら、
呼び出し側では必ず「None のときどうする?」を考える。

if で分岐する
デフォルト値を用意する
例外に変換する

など、何かしらの「扱い方」を決める。

「Optional を付けたのに、呼び出し側で何もしていない」
という状態は、設計的には危険信号です。


まとめ(Optionalは「Noneの可能性を型で約束する」ための道具)

Optional を初心者目線で整理すると、こうなります。

Optional[T] は「T かもしれないし None かもしれない」という型で、Union[T, None] の別名。Python 3.10 以降なら T | None と書ける。
戻り値に Optional を付けることで、「この関数は値がないときに None を返す」という仕様を型として明示でき、呼び出し側に「None のときどうする?」を考えさせられる。
Optional を付ける=「None を許す契約」を結ぶことなので、本当に必要なところだけに使い、「面倒だからとりあえず Optional」は避けるのが品質的に重要。
Optional をきっかけに、「None を返す設計が本当に正しいか?」「例外にすべきか?」といった設計の問いが生まれ、テストケースの抜けも見つけやすくなる。

タイトルとURLをコピーしました