Python でエラー処理を書くとき、2 つのアプローチがある。LBYL(Look Before You Leap:跳ぶ前に見よ)は事前に条件をチェックする方法、EAFP(Easier to Ask for Forgiveness than Permission:許可を求めるより許しを請う方が簡単)は例外処理に任せる方法だ。Python では EAFP が推奨されることが多いが、常にそうとは限らない。両者の使い分けを解説する。
LBYL:事前チェック方式
LBYL は、操作を実行する前に条件をチェックするスタイルだ。
# LBYL スタイル
def get_value_lbyl(data, key):
if key in data:
return data[key]
else:
return None
# ファイル操作の例
import os
def read_file_lbyl(path):
if os.path.exists(path):
if os.path.isfile(path):
with open(path) as f:
return f.read()
return None
C 言語や Java など、例外処理のコストが高い言語ではこのスタイルが一般的だ。
EAFP:例外処理方式
EAFP は、まず実行してみて、失敗したら例外で処理するスタイルだ。
# EAFP スタイル
def get_value_eafp(data, key):
try:
return data[key]
except KeyError:
return None
# ファイル操作の例
def read_file_eafp(path):
try:
with open(path) as f:
return f.read()
except (FileNotFoundError, IsADirectoryError):
return None
Python では例外処理のコストが比較的低く、EAFP が推奨されることが多い。
EAFP が推奨される理由
Python で EAFP が好まれる理由はいくつかある。
LBYL では、チェックと操作の間に状態が変わる可能性がある(TOCTOU 問題)。EAFP ではアトミックに処理される。
事前チェックが複雑になる場合、例外処理の方がシンプルに書ける。
# LBYL のレースコンディション問題
import os
def process_file_lbyl(path):
if os.path.exists(path): # チェック時点では存在する
# この間に別プロセスがファイルを削除するかもしれない
with open(path) as f: # FileNotFoundError の可能性!
return f.read()
# EAFP ならレースコンディションを気にしなくてよい
def process_file_eafp(path):
try:
with open(path) as f:
return f.read()
except FileNotFoundError:
return None
LBYL が適切な場合
EAFP が万能というわけではない。以下の場合は LBYL が適切だ。
# 例 1:例外を発生させること自体が副作用を持つ場合
class DatabaseConnection:
def execute(self, query):
# 例外が発生するとロールバックが必要など
pass
def safe_execute_lbyl(conn, query):
if conn.is_connected():
return conn.execute(query)
return None
# 例 2:失敗が頻繁に起きる場合
def get_user_age_lbyl(users, user_id):
# ユーザーが存在しないことが多いなら LBYL が効率的
if user_id in users:
return users[user_id].get("age")
return None
# 例 3:チェックが安価で、操作が高価な場合
def process_large_data_lbyl(data):
if data is not None and len(data) > 0:
# 重い処理を開始する前にチェック
return expensive_operation(data)
return None
パフォーマンスの違い
成功率が高い場合は EAFP が効率的だが、失敗率が高い場合は LBYL が効率的になることがある。
import timeit
data = {i: i for i in range(1000)}
# 存在するキーを取得(成功率 100%)
def lbyl_success():
for i in range(1000):
if i in data:
_ = data[i]
def eafp_success():
for i in range(1000):
try:
_ = data[i]
except KeyError:
pass
# EAFP の方がわずかに速い(try のオーバーヘッドが小さい)
print(timeit.timeit(lbyl_success, number=1000))
print(timeit.timeit(eafp_success, number=1000))
# 存在しないキーを取得(失敗率 100%)
def lbyl_fail():
for i in range(1000, 2000):
if i in data:
_ = data[i]
def eafp_fail():
for i in range(1000, 2000):
try:
_ = data[i]
except KeyError:
pass
# LBYL の方が速い(例外生成のコストがない)
print(timeit.timeit(lbyl_fail, number=1000))
print(timeit.timeit(eafp_fail, number=1000))
実践的な使い分け指針
操作が成功する可能性が高い。事前チェックと操作の間にレースコンディションがありうる。事前チェックのコストが高い。
操作が失敗する可能性が高い。例外発生自体が副作用を持つ。チェックが安価で操作が高価。可読性が向上する。
両方を組み合わせる
実際のコードでは、両方を適切に組み合わせることが多い。
def process_user_data(users, user_id, field):
# LBYL:基本的な型チェック
if not isinstance(users, dict):
raise TypeError("users must be a dict")
if not user_id:
return None
# EAFP:辞書アクセス
try:
user = users[user_id]
return user[field]
except KeyError:
return None
except TypeError:
# user が dict でなかった場合
return None
# より実践的な例:API クライアント
class APIClient:
def get_user(self, user_id):
# LBYL:明らかな無効入力を事前に弾く
if not user_id or not isinstance(user_id, (str, int)):
raise ValueError("Invalid user_id")
# EAFP:ネットワーク操作は例外処理
try:
response = self._request(f"/users/{user_id}")
return response.json()
except RequestError as e:
logger.error(f"Failed to get user {user_id}: {e}")
return None
hasattr と getattr の例
属性アクセスでも両方のスタイルが見られる。
# LBYL スタイル
def call_method_lbyl(obj, method_name):
if hasattr(obj, method_name):
method = getattr(obj, method_name)
if callable(method):
return method()
return None
# EAFP スタイル
def call_method_eafp(obj, method_name):
try:
method = getattr(obj, method_name)
return method()
except AttributeError:
return None
except TypeError: # callable でなかった
return None
# よりシンプル:getattr のデフォルト値を使う
def call_method_simple(obj, method_name):
method = getattr(obj, method_name, None)
if method is not None and callable(method):
return method()
return None
まとめ
LBYL と EAFP はどちらも有効なアプローチであり、状況に応じて使い分けるべきだ。
Python コミュニティでは EAFP が好まれる傾向があるが、それは「常に EAFP を使え」という意味ではない。
成功率、パフォーマンス、副作用、可読性を総合的に判断する。迷ったら両方書いてみて、より明確な方を選ぶ。
大切なのはコードの意図が明確に伝わることだ。どちらのスタイルでも、一貫性を持って使うことが重要である。