辞書にキーが存在するかどうかの確認は、プログラムの安全性と読みやすさに直結します。
本記事では、初心者の方でも確実に使い分けられるように、in演算子、dict.get、try-except(KeyError)の3つの方法を、動作の違いとコード例を交えて丁寧に解説します。
目的ごとに最適な書き方を理解して、エラーのない堅牢なコードを書けるようになりましょう。
辞書でキー存在を確認する基本
初心者が押さえるべきポイント
Pythonの辞書(dict)はキーと値の対応関係を持つデータ構造です。
キーの存在を確かめる基本には、次の3つがあります。
単にあるかどうかを知りたいならin演算子が最短で、値も同時に扱いたいならdict.get、欠損をエラーとして処理したいならtry-exceptでKeyErrorを捕捉します。
辞書のキー検索は平均O(1)で高速に動作するため、まずは可読性を優先した書き方を選ぶのが実践的です。
3つの方法の使い分け
存在確認だけならin演算子が最もシンプルで読みやすいです。
存在しない場合の代替値が必要ならdict.getが安全です。
一方、欠損自体を「例外」として扱いたい状況や、キーが存在する場合にのみ重い処理を行いたいなどの制御が必要なときはtry-exceptが適しています。
Noneが有効な値であるかどうかや、値の取り出し回数を減らしたいかなど、文脈によって最適解が変わります。
可読性とパフォーマンスの目安
in演算子は最短で高速、dict.getは存在チェックと取得を1回のアクセスで済ませられ、try-exceptは制御が柔軟です。
ただし、例外は発生時にコストが高く、通常時の高速化目的で多用するものではありません。
以下に概要をまとめます。
方法 | 記述例 | 読みやすさ | 典型用途 | 注意点 |
---|---|---|---|---|
in演算子 | if key in d: | とても高い | 有無だけ確認し、必要なら値を取り出す | d[key] との組み合わせで辞書アクセスが2回になることがある |
dict.get | v = d.get(key, default) | 高い | 存在チェックと値取得を同時に実施 | None が有効値の場合は曖昧さに注意 |
try-except | try: v = d[key] ... | 中〜高 | 欠損を例外として扱いたい、または存在時のみ処理したい | 例外は発生時のコストが高い。範囲を最小化して捕捉する |
参考として、key in d
とkey in d.keys()
は同じ真偽値になりますが、後者は余計なオブジェクトを介するため若干のオーバーヘッドが生じます。
方法1: in演算子でキー存在を判定
TrueとFalseの意味を理解する
辞書に対するin演算子は「キー」に対して判定します。
値ではない点に注意します。
# 辞書を用意します
fruits = {"apple": 3, "banana": 5}
# in演算子はキーの存在を判定します
print("apple" in fruits) # True: "apple"というキーがある
print("cherry" in fruits) # False: "cherry"というキーはない
# 値の存在はvalues()に対して判定します
print(5 in fruits.values()) # True: 値の中に5がある
True
False
True
if分岐での基本パターン
存在すれば値を取り出すという基本パターンは次のように書きます。
# 在庫数の辞書
stock = {"banana": 5, "orange": 2}
key = "banana"
if key in stock:
qty = stock[key] # ここで値を取り出す
print(f"{key} は存在します。在庫数は {qty} です。")
else:
print(f"{key} は存在しません。")
banana は存在します。在庫数は 5 です。
この書き方は読みやすく、存在確認の意図が明確です。
ただし、if key in d:
とd[key]
でハッシュ検索が最大2回発生します。
性能が致命的に効く場面はまれですが、同じキーを何度も参照する場合はdict.getやtry-exceptで1回にまとめる選択肢もあります。
dict.keysとの違い
key in d
とkey in d.keys()
は結果として同じになります。
違いは、d.keys()
が辞書のキーに対するビューを生成する点です。
通常はシンプルにkey in d
を使います。
d = {"x": 1, "y": 2}
print("x" in d) # True
print("x" in d.keys()) # True (結果は同じ)
print(1 in d) # False (inはキーを見る。値は見ない)
print(1 in d.values()) # True (値に対して判定したいとき)
True
True
False
True
方法2: dict.getで存在チェックしつつ値取得
デフォルト値で安全に扱う
dict.get(key, default)
は、キーが存在すれば値を返し、なければdefaultを返します。
存在チェックと値取得を1回で済ませられます。
# ユーザー設定。なければ規定値で動作させたいケース
settings = {"theme": "dark"} # "timeout"は存在しない
timeout = settings.get("timeout", 30) # ないときは30を使う
print(timeout)
30
この方法は、欠損を例外とせず「穏当に」処理したい場合に有効です。
規定値を与えることで分岐を減らし、コードを直線的にできます。
Noneが有効な値のときの判定
None
が有効な値として辞書に格納される場合、get
の戻り値がNone
でも「キーがない」のか「キーはあるが値がNone」なのか区別できません。
以下のようにセンチネル(一意の印)オブジェクトを使うか、素直にin
で存在確認を行います。
profile = {"name": "Taro", "memo": None} # memoは明示的にNone
# センチネルを使う方法
_SENTINEL = object()
value = profile.get("memo", _SENTINEL)
if value is _SENTINEL:
print("memo は存在しません。")
elif value is None:
print("memo は存在するが値は None です。")
else:
print(f"memo の値は {value!r} です。")
# inで存在を見分ける方法
if "memo" in profile:
print("inで確認: memo は存在します。")
memo は存在するが値は None です。
inで確認: memo は存在します。
値の更新や集計と組み合わせる
頻出パターンとして、カウンタや集計での初期値設定があります。
get
を使うと簡潔に書けます。
# 要素の出現回数を数える例
words = ["apple", "banana", "apple", "orange", "banana", "apple"]
counts = {}
for w in words:
# wがなければ0を初期値として加算する
counts[w] = counts.get(w, 0) + 1
print(counts)
{'apple': 3, 'banana': 2, 'orange': 1}
このように、存在チェックと値取得だけでなく、初期化と更新を一体化できるのがget
の利点です。
方法3: try-exceptでKeyErrorを処理
例外を使った存在チェックの書き方
[]
で辞書にアクセスしてキーがなければKeyError
が送出されます。
これを捕捉することで、欠損を明示的に扱えます。
EAFP(例外中心)の方針が適する場面で使います。
config = {"retries": 3, "timeout": 10}
for key in ["timeout", "endpoint"]:
try:
value = config[key] # キーがなければKeyError
print(f"{key} = {value}")
except KeyError:
print(f"{key} は設定されていません。")
timeout = 10
endpoint は設定されていません。
存在時のみ後続の重い処理を行いたい場合にも有効です。
例外が起きなければ通常経路で進み、欠損時だけ分岐します。
getでは判断できない場合の選び方
None
が有効値であるなど「欠損」と「None」が混同されると困るケースや、値を一度のアクセスで確実に取り出して分岐したいときは、try-exceptがわかりやすくなります。
settings = {"path": None} # pathは設定されているが値はNone
try:
path = settings["path"] # 存在すれば取り出せる(値はNoneかもしれない)
except KeyError:
print("path は未設定です。")
else:
if path is None:
print("path は設定済みですが値は None です。")
else:
print(f"path は {path} です。")
path は設定済みですが値は None です。
このように、キー欠損と値の内容を明確に分岐できます。
get
でもセンチネルを使えば同様の分岐は可能ですが、try-exceptの方が意図がはっきり伝わることがあります。
例外の範囲を最小化するコツ
例外は「本当にKeyErrorが起きうる最小の行」だけを囲むと、バグを見つけやすくなります。
また、捕捉する例外型はできるだけ限定します。
record = {"age": "20"} # ageは文字列で入っているとする
key = "age"
# 悪い例: 何が原因でexceptに入ったのか判別しづらい
try:
age_int = int(record[key]) # KeyErrorかValueErrorかが混在
except Exception:
print("何かが失敗しました。") # 広すぎる
# 良い例: KeyErrorの範囲を最小化し、他の例外は別で扱う
try:
age_str = record[key] # ここだけがKeyErrorの可能性
except KeyError:
print(f"{key} が存在しません。")
else:
try:
age_int = int(age_str) # ここではValueErrorの可能性だけに絞られる
except ValueError:
print(f"{key} は整数に変換できません。値={age_str!r}")
else:
print(f"年齢は {age_int} 歳です。")
年齢は 20 歳です。
このように例外の責務を分離することで、障害時の切り分けが容易になり、デバッグ効率が上がります。
まとめ
辞書のキー存在確認は、状況に応じて3つの方法を使い分けるのが最善です。
存在の有無だけを見るならin演算子が最も簡潔で、存在しない場合の既定値を使いたいならdict.getが便利です。
欠損を例外として扱いたい、あるいはキーが存在する場合にのみ処理を進めたいときはtry-exceptでKeyErrorを捕捉します。
特にNoneが有効な値であるときは、in
での存在確認やセンチネルの利用で曖昧さを解消してください。
まずは読みやすいコードを選び、必要に応じて辞書アクセスの回数や例外の範囲を最小化する工夫を行うと、堅牢でわかりやすいプログラムになります。