Python の and と or は単なる論理演算子ではない。短絡評価(short-circuit evaluation)という仕組みにより、条件が確定した時点で評価を打ち切り、最後に評価した値をそのまま返す。この特性を理解すると、簡潔で Python らしいコードが書けるようになる。
短絡評価の基本ルール
and と or の評価ルールは以下の通りだ。
左辺が Falsy なら左辺を返す。左辺が Truthy なら右辺を返す。
左辺が Truthy なら左辺を返す。左辺が Falsy なら右辺を返す。
重要なのは、返り値が True や False ではなく「最後に評価したオブジェクトそのもの」という点だ。
# and は左辺が Falsy なら左辺を返す print(0 and "hello") # 0 print("" and [1, 2, 3]) # "" # and は左辺が Truthy なら右辺を返す print(1 and "hello") # "hello" print([1] and {"a": 1}) # {"a": 1} # or は左辺が Truthy なら左辺を返す print(1 or "hello") # 1 print("hi" or []) # "hi" # or は左辺が Falsy なら右辺を返す print(0 or "hello") # "hello" print("" or "default") # "default"
イディオム 1:デフォルト値の設定
or を使った最も一般的なイディオムが、デフォルト値の設定だ。
# ユーザー入力が空なら "Guest" を使う name = user_input or "Guest" # 辞書から取得した値が None や空なら代替値を使う config = settings.get("timeout") or 30 # 環境変数が未設定ならデフォルトを使う import os debug_mode = os.environ.get("DEBUG") or "false"
ただし、このイディオムには落とし穴がある。0 や False も Falsy なので、これらが有効な値として使われる場合は意図しない動作になる。
# 落とし穴:0 が有効な値の場合 count = user_count or 10 # user_count が 0 だと 10 になってしまう # 対策:None との比較を明示する count = user_count if user_count is not None else 10 # Python 3.8 以降ならウォルラス演算子も使える count = 10 if (c := user_count) is None else c
イディオム 2:条件付き関数呼び出し
and を使うと、条件が成立したときだけ関数を呼び出せる。
# callback が設定されていれば呼び出す callback and callback() # debug フラグが True なら出力する debug and print(f"[DEBUG] value = {value}") # リストが空でなければ最初の要素を処理する items and process(items[0])
これは以下の if 文と等価だが、1 行で書けるため式として扱いたい場合に便利だ。
# 上記と等価な if 文 if callback: callback() if debug: print(f"[DEBUG] value = {value}") if items: process(items[0])
イディオム 3:最初の Truthy 値を取得
複数の候補から最初に見つかった Truthy 値を取得するパターン。
# 複数の設定源から最初に見つかった値を使う import os config_value = ( os.environ.get("APP_CONFIG") or load_from_file() or load_from_database() or "default" ) # ユーザー名の優先順位付き取得 display_name = user.nickname or user.username or user.email or "Anonymous"
このパターンでは、左から順に評価され、最初に Truthy な値が見つかった時点で残りは評価されない。つまり load_from_file() が Truthy を返せば load_from_database() は呼ばれない。
イディオム 4:すべての条件を満たす最後の値
and を連鎖させると、すべてが Truthy な場合のみ最後の値を返せる。
# すべての条件を満たす場合のみ結果を返す result = user and user.is_active and user.permissions and "allowed" # 上記は以下と等価 if user and user.is_active and user.permissions: result = "allowed" else: result = None # または Falsy だった値
イディオム 5:辞書やリストの安全なアクセス
短絡評価を使って、存在確認とアクセスを同時に行える。
# 辞書のネストした値を安全に取得 data = {"user": {"profile": {"name": "Alice"}}} # and でチェーンすると途中で None なら止まる name = data.get("user") and data["user"].get("profile") and data["user"]["profile"].get("name") # ただし、これは複雑になりがち # Python 3.8 以降ならウォルラス演算子で改善できる if (user := data.get("user")) and (profile := user.get("profile")): name = profile.get("name")
より現代的なアプローチとしては、dict.get() のチェーンや例外処理、あるいは専用ライブラリの使用が推奨される。
イディオム 6:三項演算子の代替
古い Python コードでは、or と and を組み合わせて三項演算子の代わりにしていた。
# 古いイディオム(Python 2.4 以前) result = condition and true_value or false_value # 現在の三項演算子(Python 2.5 以降) result = true_value if condition else false_value
ただし、古いイディオムには true_value が Falsy の場合に正しく動作しないバグがある。現在は三項演算子を使うべきだ。
# バグの例 condition = True true_value = 0 # Falsy false_value = 100 # 古いイディオム:期待は 0 だが 100 が返る result = condition and true_value or false_value print(result) # 100(バグ!) # 三項演算子:正しく 0 が返る result = true_value if condition else false_value print(result) # 0
短絡評価の副作用を利用するパターン
短絡評価では、評価されなかった式の副作用は発生しない。これを意図的に利用することもある。
# ログ出力を条件付きで行う verbose and log.debug(f"Processing {item}") # キャッシュがなければ計算する cached_value or compute_and_cache() # ファイルが存在しなければ作成する os.path.exists(path) or create_file(path)
ただし、副作用を伴う式を短絡評価に含めると、コードの意図が分かりにくくなる。可読性を優先するなら、明示的な if 文を使う方がよい場合も多い。
まとめ
短絡評価のイディオムは Python らしい簡潔なコードを書くのに役立つが、使いすぎると可読性を損なう。以下の指針を参考にするとよい。
デフォルト値の設定(value or default)、単純な条件付き実行(flag and action())など、意図が明確なパターン。
複雑なネスト、副作用を伴う複数の式、0 や False が有効値になりうる場面。明示的な if 文や三項演算子を使う方がよい。