9. ブール論理で条件を組み合わせる
第7章では、ブール値と、比較演算子を使った単純な条件について学びました。第8章では、これらの条件を使って if 文で意思決定を行いました。しかし、実際のプログラムでは、複数の条件を一度にチェックする必要があることがよくあります。ユーザーが正しいパスワードを持っていてかつログインしている場合にアクセスを許可すべきでしょうか?温度が暑すぎるまたは寒すぎる場合に警告を表示すべきでしょうか?ファイルが空ではない場合に処理を進めるべきでしょうか?
Python には、ブール値を組み合わせたり変更したりできる 3 つの 論理演算子 (logical operators) があります。and、or、not です。これらの演算子は、プログラム内で複雑な意思決定ロジックを表現するための基本要素になります。
9.1) 論理演算子 and、or、not
3 つの論理演算子は、ブール値(またはブールとして扱える値)とともに動作し、新しいブールの結果を生成します。
9.1.1) and 演算子
and 演算子は、両方のオペランドが真のときにのみ True を返します。どちらか一方でも偽なら、式全体は偽になります。
# 両方の条件が True でなければならない
age = 25
has_license = True
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: True
# どちらかの条件が False なら、結果は False
age = 18
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: Falseand は厳格な門番だと考えてください。全体のチェックが成功するには、すべての条件を通過する必要があります。
and の真理値表:
| 左オペランド | 右オペランド | 結果 |
|---|---|---|
True | True | True |
True | False | False |
False | True | False |
False | False | False |
9.1.2) or 演算子
or 演算子は、少なくとも一方のオペランドが真のときに True を返します。両方のオペランドが偽のときにのみ False を返します。
# 少なくとも 1 つの条件が True でなければならない
is_weekend = True
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: True
# 両方の条件が False
is_weekend = False
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: Falseor は寛容な門番だと考えてください。通過するには、1 つの条件を満たすだけで十分です。
or の真理値表:
| 左オペランド | 右オペランド | 結果 |
|---|---|---|
True | True | True |
True | False | True |
False | True | True |
False | False | False |
割引の適用可否システムの実用例を見てみましょう。
# 学生 OR シニアなら割引を受けられる
age = 68
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: True
# 別の顧客
age = 30
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: False最初の顧客は、学生ではないものの、条件のどれか(シニア)を満たしているため割引の対象になります。
9.1.3) not 演算子
not 演算子は 単項演算子 (unary operator)(単一のオペランドに対して動作する演算子)で、ブール値を反転させます。True を False にし、False を True にします。
is_raining = False
is_sunny = not is_raining
print(is_sunny) # Output: True
is_raining = True
is_sunny = not is_raining
print(is_sunny) # Output: Falsenot の真理値表:
| オペランド | 結果 |
|---|---|
True | False |
False | True |
not 演算子は、条件の反対をチェックしたいときに特に便利です。
# ファイルが空ではないかどうかをチェックする
file_size = 0
is_empty = file_size == 0
is_not_empty = not is_empty
print(f"File has content: {is_not_empty}") # Output: File has content: False
# ユーザーがログインしていないかどうかをチェックする
is_logged_in = False
needs_login_prompt = not is_logged_in
print(f"Show login prompt: {needs_login_prompt}") # Output: Show login prompt: True9.1.4) 複数の論理演算子を組み合わせる
より高度な条件を作るために、1 つの式の中で複数の論理演算子を組み合わせられます。
# オンラインストア: 注文が $50 以上 OR プレミアム会員なら送料無料
# さらに在庫があること
order_total = 45.00
is_premium = True
in_stock = True
gets_free_shipping = (order_total >= 50 or is_premium) and in_stock
print(f"Free shipping: {gets_free_shipping}") # Output: Free shipping: Trueこの評価を追ってみましょう:
order_total >= 50はFalseと評価されます(45.00 は 50 以上ではありません)is_premiumはTrueですFalse or TrueはTrueと評価されますin_stockはTrueですTrue and TrueはTrueと評価されます
アクセス制御の別の例です。
# ユーザーが管理者なら管理パネルにアクセスできる
# そして(社内ネットワーク上にいる OR VPN を使っている)
is_admin = True
on_internal_network = False
using_vpn = True
can_access_admin = is_admin and (on_internal_network or using_vpn)
print(f"Can access admin panel: {can_access_admin}") # Output: Can access admin panel: True(on_internal_network or using_vpn) の周りのかっこに注目してください。算術式のかっこと同様、評価順序を制御するため重要です。
9.2) ブール式における演算子の優先順位(not、and、or の順)
かっこなしで複数の論理演算子を組み合わせると、Python は特定の優先順位ルールに従って評価順序を決定します。これらのルールを理解すると、正しい条件を書けるようになり、見えにくいバグを避けられます。
9.2.1) 優先順位の階層
Python は論理演算子を次の順序(優先順位が高い順)で評価します。
not(最優先)and(中)or(最下位)
つまり、not が最初に評価され、次に and、最後に or です。
# かっこがない場合、優先順位が順序を決める
result = True or False and False
print(result) # Output: True
# Python がこれを評価する方法:
# Step 1: False and False → False(and は or より優先順位が高い)
# Step 2: True or False → Trueより詳細な例で、手順を追ってみましょう。
is_weekend = False
is_holiday = True
has_work = True
# 式: not has_work or is_weekend and is_holiday
free_time = not has_work or is_weekend and is_holiday
# 評価順序:
# Step 1: not has_work → not True → False
# Step 2: is_weekend and is_holiday → False and True → False
# Step 3: False or False → False
print(f"Has free time: {free_time}") # Output: Has free time: False9.2.2) 明確さのためにかっこを使う
優先順位ルールを理解していても、かっこを使うとコードがより明確になり、ミスを防げます。かっこはデフォルトの優先順位を上書きし、意図をはっきりさせます。
# かっこがないと曖昧
result = True or False and False
print(result) # Output: True
# かっこがあると明確 - 本当に意味したかったのはどちら?
result = (True or False) and False
print(result) # Output: False
result = True or (False and False)
print(result) # Output: Trueこの 2 つの式は異なる結果になります!かっこによって意味が完全に変わります。
9.2.3) 比較演算子と論理演算子を一緒に使う
比較演算子(<、>、==、!= など)は、論理演算子より 優先順位が高い です。つまり、論理演算より先に比較が評価されます。
age = 25
income = 50000
# 比較の周りにかっこは不要
eligible = age >= 18 and income >= 30000
print(f"Eligible for loan: {eligible}") # Output: Eligible for loan: True
# Python は次のように評価する:
# Step 1: age >= 18 → True
# Step 2: income >= 30000 → True
# Step 3: True and True → True9.3) 短絡評価
Python は、and と or を使ったブール式を評価するときに 短絡評価 (short-circuit evaluation) を行います。つまり、最終結果が分かった時点で評価を止め、後続のオペランドの評価を省略する可能性があります。この挙動はパフォーマンス最適化であると同時に、有用なプログラミング手法でもあります。
9.3.1) and が短絡する仕組み
and 演算子では、左オペランドが False なら、式全体が必ず False になることが分かります(and が True を返すには両方のオペランドが真である必要があるためです)。そのため、Python は右オペランドをまったく評価しません。
# 単純なデモ
x = 5
result = x < 3 and x > 10
print(result) # Output: False
# Python の評価:
# Step 1: x < 3 → 5 < 3 → False
# Step 2: 左側が False なので、x > 10 は評価しない
# Step 3: False を返す短絡評価がなぜ重要かを示す実用例です。
# 数が割り切れるかをチェック - ゼロ除算を避ける
numerator = 100
denominator = 0
# これは短絡評価のおかげで安全
# denominator が 0 なら除算は発生しない
is_divisible = denominator != 0 and numerator % denominator == 0
print(f"Is divisible: {is_divisible}") # Output: Is divisible: False
# 短絡評価がなければ、これはエラーになります:
# denominator = 0
# result = numerator % denominator # ZeroDivisionError!denominator != 0 が False と評価されるため、Python はゼロ除算エラーを引き起こす numerator % denominator を評価しません。
文字列操作の別の例も見てみましょう。
# 文字列プロパティを安全にチェックする
text = ""
# text が空ではない AND 先頭文字が大文字であるかをチェックする
# text が空なら text[0] にアクセスしないので安全
has_uppercase_start = len(text) > 0 and text[0].isupper()
print(f"Starts with uppercase: {has_uppercase_start}") # Output: Starts with uppercase: False
# 長さチェックなしでこれを試すと:
# text = ""
# result = text[0].isupper() # IndexError: string index out of range9.3.2) or が短絡する仕組み
or 演算子では、左オペランドが True なら、式全体が必ず True になることが分かります(少なくとも一方が真であれば十分なためです)。そのため、Python は右オペランドを評価しません。
# 単純なデモ
x = 15
result = x > 10 or x < 5
print(result) # Output: True
# Python の評価:
# Step 1: x > 10 → 15 > 10 → True
# Step 2: 左側が True なので、x < 5 は評価しない
# Step 3: True を返す9.3.3) 短絡評価の実用的な用途
エラーを回避する:
# リスト要素へ安全にアクセスする
numbers = [1, 2, 3]
index = 5
# アクセス前に index が有効かどうかをチェックする
is_valid = index < len(numbers) and numbers[index] > 0
print(f"Valid and positive: {is_valid}") # Output: Valid and positive: False
# 短絡がなければ、これはクラッシュします:
# is_valid = numbers[index] > 0 # IndexError!複数条件を効率的にチェックする:
# フォーム検証 - 最初のエラーで止める
email = "user@example.com"
password = "pass"
age = 25
# 失敗しやすい順に各要件をチェックする
valid_form = (
len(email) > 0 and # Quick check
"@" in email and # Quick check
len(password) >= 8 and # Quick check
age >= 18 # Quick check
)
print(f"Form valid: {valid_form}") # Output: Form valid: False
# Stops at password length check, doesn't evaluate age9.4) 非ブールオペランドで and / or が返す値と、よくあるブール式の落とし穴
ここまでは、and、or、not がブール値と一緒に動作する例を見てきました。しかし Python の論理演算子には興味深い挙動があります。True と False だけでなく、あらゆる値を扱えます。この挙動を理解すると、より簡潔なコードを書けるようになり、よくあるミスも避けられます。
9.4.1) Truthy と Falsy を理解する(復習)
第7章で学んだように、Python は多くの非ブール値を、ブール文脈では「truthy」または「falsy」として扱います。
falsy 値(False として扱われる):
FalseNone0(任意の数値型の 0)""(空文字列)[](空のリスト){}(空の辞書)()(空のタプル)
truthy 値(True として扱われる):
True- 0 ではない任意の数
- 空でない任意の文字列
- 空でない任意のコレクション
# truthy をデモする
if "hello":
print("Non-empty strings are truthy") # Output: Non-empty strings are truthy
if 0:
print("This won't print") # Zero is falsy
else:
print("Zero is falsy") # Output: Zero is falsy
if [1, 2, 3]:
print("Non-empty lists are truthy") # Output: Non-empty lists are truthy9.4.2) and が実際に返す値
and 演算子は常に True または False を返すわけではありません。代わりに、オペランドのどちらかを返します。
- 左オペランドが falsy なら、
andは左オペランドを返します(右は評価しません) - 左オペランドが truthy なら、
andは右オペランドを返します
# and は最初の falsy 値、またはすべて truthy なら最後の値を返す
result = 5 and 10
print(result) # Output: 10
result = 0 and 10
print(result) # Output: 0
result = "hello" and "world"
print(result) # Output: world
result = "" and "world"
print(result) # Output: (empty string)
result = None and "world"
print(result) # Output: Noneこれらの例を追ってみましょう。
# Example 1: Both truthy
result = 5 and 10
# Step 1: 5 is truthy, so evaluate right side
# Step 2: Return right side value: 10
print(result) # Output: 10
# Example 2: Left is falsy
result = 0 and 10
# Step 1: 0 is falsy, so return it immediately
# Step 2: Don't evaluate right side
print(result) # Output: 0
# Example 3: Both truthy strings
result = "hello" and "world"
# Step 1: "hello" is truthy, so evaluate right side
# Step 2: Return right side value: "world"
print(result) # Output: world9.4.3) or が実際に返す値
同様に、or 演算子もオペランドのどちらかを返します。
- 左オペランドが truthy なら、
orは左オペランドを返します(右は評価しません) - 左オペランドが falsy なら、
orは右オペランドを返します
# or は最初の truthy 値、またはすべて falsy なら最後の値を返す
result = 5 or 10
print(result) # Output: 5
result = 0 or 10
print(result) # Output: 10
result = "" or "default"
print(result) # Output: default
result = "hello" or "world"
print(result) # Output: hello
result = None or 0
print(result) # Output: 0これらの例を追ってみましょう。
# Example 1: Left is truthy
result = 5 or 10
# Step 1: 5 is truthy, so return it immediately
# Step 2: Don't evaluate right side
print(result) # Output: 5
# Example 2: Left is falsy
result = 0 or 10
# Step 1: 0 is falsy, so evaluate right side
# Step 2: Return right side value: 10
print(result) # Output: 10
# Example 3: Both falsy
result = None or 0
# Step 1: None is falsy, so evaluate right side
# Step 2: Return right side value: 0 (even though it's also falsy)
print(result) # Output: 09.4.4) デフォルト値に or を使う実用例
よくあるパターンの 1 つは、or を使ってデフォルト値を用意することです。
# デフォルト付きのユーザー設定
user_theme = "" # ユーザーがテーマを設定していない
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: light
user_theme = "dark"
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: dark
# 設定値
max_retries = None # 未設定
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 3
max_retries = 5
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 5このパターンが動くのは、左側が falsy(空文字列、None、0 など)の場合に or が右側(デフォルト値)を返すからです。
9.4.12) 演算子が返す値のまとめ
それぞれの論理演算子が返す値について、包括的にまとめます。
and 演算子:
- 最初の falsy オペランドを返します
- すべてのオペランドが truthy の場合は最後のオペランドを返します
- 短絡評価を使います(最初の falsy 値で停止します)
or 演算子:
- 最初の truthy オペランドを返します
- すべてのオペランドが falsy の場合は最後のオペランドを返します
- 短絡評価を使います(最初の truthy 値で停止します)
not 演算子:
- 常にブール値(
TrueまたはFalse)を返します notはオペランドをブールに変換してから否定します
# 3 つの演算子すべてをデモする
print(5 and 10) # Output: 10 (both truthy, return last)
print(0 and 10) # Output: 0 (first falsy, return it)
print(5 or 10) # Output: 5 (first truthy, return it)
print(0 or 10) # Output: 10 (first falsy, evaluate second)
print(not 5) # Output: False (5 is truthy, not returns boolean)
print(not 0) # Output: True (0 is falsy, not returns boolean)
print(not "") # Output: True (empty string is falsy)
print(not "hello") # Output: False (non-empty string is truthy)これらの挙動を理解すると、より簡潔で Pythonic なコードを書けるようになりますが、常に明確さを優先してください。これらの機能を使うことでコードが理解しにくくなるなら、明示的に書くほうが良いです。
この章では、Python の and、or、not 演算子を使って、単純な条件を複雑なブール論理へ組み合わせる方法を学びました。演算子の優先順位、短絡評価、そして非ブール値に対する論理演算子の意外な挙動についても学びました。また、よくある落とし穴と、読みやすく正しいブール式を書くためのベストプラクティスも確認しました。
これらの道具により、プログラム内で高度な意思決定ロジックを表現できるようになります。第8章の if 文と組み合わせれば、プログラムに必要な条件ロジックのほぼすべてを扱えるようになります。次の章では、条件式について学びます。条件式は、条件に基づいて 2 つの値のどちらかを選ぶためのコンパクトな方法を提供します。