Python では try-except を条件分岐の代わりに使うパターンがある。特に「許可を求めるより許しを請う方が簡単」(EAFP)の思想に基づき、まず実行してみて失敗したら対処するというスタイルだ。この記事では、try-except を条件分岐的に使う実践パターンを紹介する。
辞書アクセス:KeyError をキャッチ
辞書からキーを取得するとき、キーの存在確認を省略して例外で処理できる。
data = {"name": "Alice", "age": 30}
# 条件分岐スタイル
def get_with_if(data, key):
if key in data:
return data[key]
return "デフォルト"
# try-except スタイル
def get_with_try(data, key):
try:
return data[key]
except KeyError:
return "デフォルト"
# 最もシンプル:dict.get を使う
def get_with_method(data, key):
return data.get(key, "デフォルト")
dict.get() が最もシンプルだが、デフォルト値を動的に計算したい場合は try-except が有用だ。
def get_or_compute(cache, key, compute_func):
try:
return cache[key]
except KeyError:
value = compute_func(key)
cache[key] = value
return value
# 使用例
cache = {}
result = get_or_compute(cache, "x", lambda k: expensive_computation(k))
型変換:ValueError をキャッチ
ユーザー入力を数値に変換するとき、try-except パターンが自然だ。
def parse_int_with_if(s):
# 条件分岐:事前チェックが複雑
if s.lstrip("-").isdigit():
return int(s)
return None
def parse_int_with_try(s):
# try-except:シンプル
try:
return int(s)
except ValueError:
return None
# テスト
print(parse_int_with_try("42")) # 42
print(parse_int_with_try("-10")) # -10
print(parse_int_with_try("3.14")) # None
print(parse_int_with_try("hello")) # None
float への変換も同様だ。
def parse_number(s):
# まず int を試し、ダメなら float を試す
try:
return int(s)
except ValueError:
try:
return float(s)
except ValueError:
return None
print(parse_number("42")) # 42 (int)
print(parse_number("3.14")) # 3.14 (float)
print(parse_number("abc")) # None
属性アクセス:AttributeError をキャッチ
オブジェクトの属性が存在するかどうかを確認する場合。
class User:
def __init__(self, name):
self.name = name
class Admin(User):
def __init__(self, name, permissions):
super().__init__(name)
self.permissions = permissions
def get_permissions_with_if(user):
if hasattr(user, "permissions"):
return user.permissions
return []
def get_permissions_with_try(user):
try:
return user.permissions
except AttributeError:
return []
# 使用例
user = User("Alice")
admin = Admin("Bob", ["read", "write"])
print(get_permissions_with_try(user)) # []
print(get_permissions_with_try(admin)) # ["read", "write"]
インデックスアクセス:IndexError をキャッチ
リストの範囲外アクセスを安全に処理する。
def safe_get_with_if(lst, index):
if 0 <= index < len(lst):
return lst[index]
return None
def safe_get_with_try(lst, index):
try:
return lst[index]
except IndexError:
return None
# 負のインデックスも考慮する場合、if は複雑になる
def safe_get_with_if_full(lst, index):
if -len(lst) <= index < len(lst):
return lst[index]
return None
# try-except なら負のインデックスも自然に処理できる
items = ["a", "b", "c"]
print(safe_get_with_try(items, 10)) # None
print(safe_get_with_try(items, -1)) # "c"
print(safe_get_with_try(items, -10)) # None
ファイル操作:複数の例外をキャッチ
ファイル操作では複数の例外が発生しうるため、try-except が適している。
def read_json_config(path):
import json
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError:
return {} # ファイルがなければ空の設定
except json.JSONDecodeError:
return {} # JSON が壊れていれば空の設定
except PermissionError:
raise # 権限エラーは再送出して呼び出し側に任せる
イテレーション:StopIteration をキャッチ
イテレータから次の要素を安全に取得する。
def first_or_default(iterable, default=None):
try:
return next(iter(iterable))
except StopIteration:
return default
# 使用例
print(first_or_default([1, 2, 3])) # 1
print(first_or_default([])) # None
print(first_or_default([], "empty")) # "empty"
ただし、next() にはデフォルト値を指定できるため、通常はそちらを使う。
def first_or_default_simple(iterable, default=None):
return next(iter(iterable), default)
複数の例外を一度にキャッチ
関連する複数の例外をまとめて処理できる。
def safe_divide(a, b):
try:
return a / b
except (ZeroDivisionError, TypeError):
return None
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # None
print(safe_divide(10, "a")) # None
else 節と finally 節の活用
try-except には else と finally を組み合わせられる。
def process_data(data):
try:
result = transform(data)
except ValueError as e:
print(f"変換エラー: {e}")
return None
else:
# 例外が発生しなかった場合のみ実行
print("変換成功")
return result
finally:
# 成功・失敗に関わらず実行
cleanup()
else 節は、try ブロック内のコードを最小限に保ちつつ、成功時の処理を明確に分離できる。
パターン:複数の方法を順に試す
複数の変換方法を順に試すパターン。
def parse_date(s):
from datetime import datetime
formats = [
"%Y-%m-%d",
"%Y/%m/%d",
"%d-%m-%Y",
"%m/%d/%Y",
]
for fmt in formats:
try:
return datetime.strptime(s, fmt)
except ValueError:
continue
return None # どのフォーマットにも合わなかった
print(parse_date("2024-01-15")) # 成功
print(parse_date("01/15/2024")) # 成功
print(parse_date("invalid")) # None
注意点:例外を広くキャッチしすぎない
except Exception や素の except: は避けるべきだ。
# 悪い例:すべての例外をキャッチ
def bad_parse(s):
try:
return int(s)
except: # KeyboardInterrupt なども捕まえてしまう
return None
# 悪い例:Exception は広すぎる
def still_bad_parse(s):
try:
return int(s)
except Exception: # プログラミングエラーも隠してしまう
return None
# 良い例:具体的な例外のみキャッチ
def good_parse(s):
try:
return int(s)
except ValueError:
return None
まとめ
try-except を条件分岐代わりに使うパターンは Python では一般的だ。
辞書・リストアクセス、型変換、ファイル操作など、失敗の可能性がある操作。複数の例外をまとめて処理したい場合。
キャッチする例外は具体的に指定する。プログラミングエラー(TypeError、AttributeError の一部)は通常キャッチしない。パフォーマンスが重要で失敗率が高い場合は事前チェックを検討する。
try-except は Python の EAFP 文化を体現するイディオムであり、適切に使えばコードが簡潔で堅牢になる。