【Python型ヒント】Optional型の本当の意味知ってる?Noneを許容する正しい使い方

こんにちは。ゆうせいです。

Pythonで型ヒントを使い始めると、コードの安全性がぐっと高まり、読みやすくもなりますよね。name: strage: int のように、変数や関数の引数がどんな型を持つべきかを明示するのは、もはや現代的な開発の常識です。

ですが、こんな時どうすれば良いか迷ったことはありませんか? 🤔

「ユーザーをIDで検索する関数を作ったけど、もし見つからなかったら None を返したい…」

この「文字列を返すかもしれないし、None を返すかもしれない」という状況を、型ヒントでどう表現すれば良いのでしょうか。その答えこそが、今回ご紹介する typing モジュールの Optional 型です!


None が返る可能性のある、よくあるケース

まず、具体的なコードで考えてみましょう。

ユーザーIDをキー、ユーザー名を値として持つ辞書から、ユーザーを検索する関数を作ります。

# ユーザーデータを辞書で代用
users_db = {
    1: "Sato",
    2: "Suzuki",
    3: "Takahashi",
}

# IDが見つかればユーザー名(str)を、見つからなければNoneを返す関数
def find_user_by_id(user_id: int): # -> ???
    return users_db.get(user_id)

この find_user_by_id 関数の返り値の型ヒント (-> の後ろ) は、何と書くのが正しいでしょうか?

-> str と書いてしまうと、「この関数は必ず文字列を返します」という嘘をつくことになります。IDが見つからなかった場合に None が返ってくると、型ヒントと実際の振る舞いが食い違ってしまい、バグの原因になってしまいます。


救世主 Optional の登場 ✨

ここで登場するのが Optional です。typing モジュールからインポートして使います。

from typing import Optional

# ユーザーデータを辞書で代用
users_db = {
    1: "Sato",
    2: "Suzuki",
    3: "Takahashi",
}

def find_user_by_id(user_id: int) -> Optional[str]:
    return users_db.get(user_id)

# 実際に使ってみる
user_name = find_user_by_id(2) # Suzuki
not_found = find_user_by_id(99) # None

print(user_name)
print(not_found)

返り値の型ヒントを -> Optional[str] と書くことで、「この関数は、文字列(str)または None を返します」という意味を正確に表現できます。これで、関数の使い方を見ただけで、返り値が None になる可能性を考慮すべきだと一目で分かりますね!


Optional型の正体は Union

実は、Optional 型には秘密があります。それは、Optional[X] という書き方が、Union[X, None] という書き方の、ただの別名(エイリアス)だということです。

Union は、複数の型を取りうることを示す型ヒントです。つまり、Union[str, None] は「文字列または None」という意味で、Optional[str] と完全に同じなのです。Optional は、この None を許容するケースが非常によく使われるため、特別に用意された便利なショートカットというわけですね。


よくある誤解: 「任意引数」との違い ⚠️

Optional という名前から、「関数の引数を任意にする(省略可能にする)ためのものだ」と誤解してしまうことがありますが、これは間違いです!

  • 任意引数 (Optional Argument): 引数にデフォルト値を設定し、呼び出し時に省略できるようにすること。def greet(name: str = "Guest"): ...
  • Optional型 (Optional Type): 変数や返り値が、特定の型または None のどちらかをとりうると示すこと。-> Optional[str]

この二つは全く異なる概念なので、混同しないように注意しましょう!


Optionalを使う最大のメリット: バグの事前検知

Optional を使う最大のメリットは、コードの意図が明確になることに加え、None に起因するエラーを未然に防げることです。

find_user_by_id(99) の結果が None であることを忘れて、次のようなコードを書いてしまったとします。

user_name = find_user_by_id(99)
print(user_name.upper()) # 大文字にしようとする

これを実行すると、AttributeError: 'NoneType' object has no attribute 'upper' という、誰もが一度は見たことのあるエラーが発生します。

しかし、MyPyのような静的解析ツールを使うと、コードを実行する前にこの問題を検知してくれます。user_nameOptional[str] 型なので None の可能性があるため、「None でないことをチェックせずに .upper() を呼び出すのは危険ですよ!」と警告してくれるのです。

安全なコードは、次のように書きます。

user_name = find_user_by_id(99)

if user_name is not None:
    # このブロックの中では、user_nameがstrであることが保証される
    print(user_name.upper())
else:
    print("ユーザーが見つかりませんでした。")

この一手間が、信頼性の高いソフトウェアを作る上で非常に重要になります。


これからの学習に向けて

Optional[X] は、「値が X 型または None の可能性がある」ことを示す、非常に重要な型ヒントです。

関数が None を返す可能性がある場合や、変数が初期状態で None を持つ場合には、積極的に Optional を使っていきましょう。この習慣が、あなたのコードをより安全で、他の開発者にとっても親切なものに変えてくれます。

型ヒントの世界は奥が深いですが、まずは Optional を使いこなし、None にまつわるエラーを撲滅することから始めてみてくださいね! 👍

セイ・コンサルティング・グループの新人エンジニア研修のメニューへのリンク

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。