【Python型ヒント】Optional型の本当の意味知ってる?Noneを許容する正しい使い方
こんにちは。ゆうせいです。
Pythonで型ヒントを使い始めると、コードの安全性がぐっと高まり、読みやすくもなりますよね。name: str
や age: 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_name
は Optional[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
にまつわるエラーを撲滅することから始めてみてくださいね! 👍