Pythonの真偽値評価で知っておくべき6つの落とし穴


こんにちは、とまだです。

Python アドベントカレンダー 2024 のうち、1 日目の記事をお届けします！

https://qiita.com/advent-calendar/2024/python

こんにちは、とまだです。

突然ですが、以下のコードの実行結果が分かりますか？

```python
x = range(0)
if x:
    print("これは実行される？")
```

`x` の中身は空の `range` オブジェクトですが、あくまでオブジェクトなのでちょっと難しいと思うかもしれません。

ただ、答えは「空の range()オブジェクトだから実行されない」です。

```bash
$ python main.py
$
```

Python では `range(0)` は空のシーケンスを表すため、`if` 文の条件式は `False` になります。

でも、こんなコードはどうでしょう？

```python
y = range(0)
z = range(0)
print(y == z)
print(y is z)
```

先ほどと同じく、`range(0)` は空のシーケンスですが、比較演算子によって結果が異なります。

```bash
$ python main.py
True
False
```

`==` 演算子では `True` になりますが、`is` 演算子では `False` になります。

同じ range(0) なのに、比較演算子によって結果が変わってしまうんですね...。

これは Python の真偽値評価における「落とし穴」の 1 つです。

今回は、私が実際に開発中にハマってしまった（もしくは他の人がハマっているのを見かけた）真偽値評価の落とし穴を 7 つ紹介します。

## この記事で学べること

- Python の真偽値評価の基本的な仕組み
- よくある落とし穴とその対処法
- バグを未然に防ぐための対策

## 真偽値はプログラミングの基本

真偽値評価は、条件分岐やループなど、プログラミングの基本中の基本です。

しかし、Python の真偽値評価には他の言語とは異なる独特の仕組みがあり、これが思わぬバグの原因になることがあります。

では、具体的な落とし穴を見ていきましょう！

## 6 つの落とし穴

### 落とし穴 1：数値のゼロ判定

数値のゼロ判定で思わぬバグが発生することがあります。

```python
x = 0.0
print(x == 0)    # True
print(bool(x))   # False
print(x is 0)    # False
```

特に `is` 演算子を使った比較は要注意です。`is` は同一性（オブジェクト ID が同じか）を確認するため、数値の比較には適していません。

```python
# 🙅‍♂️ ダメな例：isを使った比較
if x is 0:
    print("実行されない")

# 🙆‍♂️ 良い例：==を使った比較
if x == 0:
    print("実行される")
```

### 落とし穴 2：文字列の真偽値評価

空白文字を含む文字列の評価結果は、直感に反することがあります。

```python
print(bool(""))      # False: 空文字
print(bool(" "))     # True: スペース1つ
print(bool("\n"))    # True: 改行のみ
```

ユーザー入力を処理する際など、この違いは重要です。

```python
# 🙅‍♂️ ダメな例：空白文字のチェックを忘れる
def validate_input(text):
    if text:
        return True
    return False

# 🙆‍♂️ 良い例：strip()で空白文字を除去してからチェック
def validate_input(text):
    if text.strip():
        return True
    return False
```

### 落とし穴 3：and/or の戻り値

`and` や `or` は、最後に評価された値をそのまま返します。これは他の言語にはない Python の特徴です。

```python
print("hello" and [])     # []
print("" or "world")      # "world"
print([] or {} or "last") # "last"
```

この性質を利用して、デフォルト値を設定することができます。

```python
# 🙆‍♂️ 良い例：デフォルト値の設定
config = user_config or {}
name = user_input.strip() or "Anonymous"
```

ただし、複雑な条件になると読みにくくなるので注意が必要です。

```python
# 🙅‍♂️ ダメな例：複雑すぎる条件
result = (a and b) or (c and d) or default

# 🙆‍♂️ 良い例：if文で明示的に
if a and b:
    result = b
elif c and d:
    result = d
else:
    result = default
```

### 落とし穴 4：None との比較

`None` との比較は、思わぬバグの原因になりやすいです。

```python
x = None
print(x == False)  # False
print(x is False)  # False
print(bool(x))     # False
```

`None` は `False` とは異なるオブジェクトですが、真偽値評価では `False` として扱われます。

```python
# 🙅‍♂️ ダメな例：==での比較
if x == None:
    print("実行されない")

# 🙆‍♂️ 良い例：isを使用
if x is None:
    print("実行される")
```

この場合は逆に `is` を使うのが Python のイディオムです。

### 落とし穴 5：演算子の優先順位

`and`、`or`、`not` の優先順位は、直感とは異なることがあります。

```python
print(True or False and False)  # True
print((True or False) and False)  # False
print(not True or False)  # False
print(not (True or False))  # False
```

複雑な条件は、括弧を使って明示的に優先順位を示すことをおすすめします。

```python
# 🙅‍♂️ ダメな例：優先順位があいまい
if a and b or c and d:
    print("何が起こる？")

# 🙆‍♂️ 良い例：括弧で明示
if (a and b) or (c and d):
    print("意図が明確")
```

### 落とし穴 6：カスタムクラスの真偽値評価

カスタムクラスを作る場合、真偽値評価の挙動を制御できます。

```python
class CustomContainer:
    def __init__(self, items):
        self.items = items

    # __bool__が定義されていない場合は__len__が使用される
    def __len__(self):
        return len(self.items)

empty_container = CustomContainer([])
print(bool(empty_container))  # False
```

`__bool__` メソッドを定義しない場合、`__len__` メソッドの戻り値が使用されます。

もう少し正確に言うと、`__bool__` が定義されていない場合は、`__len__` が `0` の場合に `False` と評価されます。
（ややこしい）

そのため、以下のように `__bool__` メソッドを明示的に定義することをおすすめします。

```python
# 🙆‍♂️ 良い例：明示的に__bool__を定義
class CustomContainer:
    def __init__(self, items):
        self.items = items

    def __bool__(self):
        return bool(self.items)
```

これにより、クラスのインスタンスの真偽値評価をカスタマイズできます。

## まとめ

今回紹介した 7 つの落とし穴を整理すると、以下のようになります。

1. 数値のゼロ判定は `==` を使う
2. 空白文字は空文字とは異なる
3. `and/or` は最後に評価された値を返す
4. `None` との比較は `is` を使う
5. 複雑な条件は括弧を使って明示する
6. カスタムクラスは `__bool__` で挙動を制御できる

これらの落とし穴を知っておくことで、より安全な Python コードを書くことができます。

最後まで読んでいただき、ありがとうございました！

## 他にもアドベントカレンダー記事を書いています！

他にも、2024 年のアドベントカレンダーに参加しています。

以下の記事でまとめているので、よければ他の記事も読んでいただけると嬉しいです！

https://qiita.com/tomada/items/025b3745df8c484f5856