こんにちは、とまだです。
Python アドベントカレンダー 2024 のうち、1 日目の記事をお届けします!
こんにちは、とまだです。
突然ですが、以下のコードの実行結果が分かりますか?
x = range(0)
if x:
print("これは実行される?")
x
の中身は空の range
オブジェクトですが、あくまでオブジェクトなのでちょっと難しいと思うかもしれません。
ただ、答えは「空の range()オブジェクトだから実行されない」です。
$ python main.py
$
Python では range(0)
は空のシーケンスを表すため、if
文の条件式は False
になります。
でも、こんなコードはどうでしょう?
y = range(0)
z = range(0)
print(y == z)
print(y is z)
先ほどと同じく、range(0)
は空のシーケンスですが、比較演算子によって結果が異なります。
$ python main.py
True
False
==
演算子では True
になりますが、is
演算子では False
になります。
同じ range(0) なのに、比較演算子によって結果が変わってしまうんですね...。
これは Python の真偽値評価における「落とし穴」の 1 つです。
今回は、私が実際に開発中にハマってしまった(もしくは他の人がハマっているのを見かけた)真偽値評価の落とし穴を 7 つ紹介します。
この記事で学べること
- Python の真偽値評価の基本的な仕組み
- よくある落とし穴とその対処法
- バグを未然に防ぐための対策
真偽値はプログラミングの基本
真偽値評価は、条件分岐やループなど、プログラミングの基本中の基本です。
しかし、Python の真偽値評価には他の言語とは異なる独特の仕組みがあり、これが思わぬバグの原因になることがあります。
では、具体的な落とし穴を見ていきましょう!
6 つの落とし穴
落とし穴 1:数値のゼロ判定
数値のゼロ判定で思わぬバグが発生することがあります。
x = 0.0
print(x == 0) # True
print(bool(x)) # False
print(x is 0) # False
特に is
演算子を使った比較は要注意です。is
は同一性(オブジェクト ID が同じか)を確認するため、数値の比較には適していません。
# 🙅♂️ ダメな例:isを使った比較
if x is 0:
print("実行されない")
# 🙆♂️ 良い例:==を使った比較
if x == 0:
print("実行される")
落とし穴 2:文字列の真偽値評価
空白文字を含む文字列の評価結果は、直感に反することがあります。
print(bool("")) # False: 空文字
print(bool(" ")) # True: スペース1つ
print(bool("\n")) # True: 改行のみ
ユーザー入力を処理する際など、この違いは重要です。
# 🙅♂️ ダメな例:空白文字のチェックを忘れる
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 の特徴です。
print("hello" and []) # []
print("" or "world") # "world"
print([] or {} or "last") # "last"
この性質を利用して、デフォルト値を設定することができます。
# 🙆♂️ 良い例:デフォルト値の設定
config = user_config or {}
name = user_input.strip() or "Anonymous"
ただし、複雑な条件になると読みにくくなるので注意が必要です。
# 🙅♂️ ダメな例:複雑すぎる条件
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
との比較は、思わぬバグの原因になりやすいです。
x = None
print(x == False) # False
print(x is False) # False
print(bool(x)) # False
None
は False
とは異なるオブジェクトですが、真偽値評価では False
として扱われます。
# 🙅♂️ ダメな例:==での比較
if x == None:
print("実行されない")
# 🙆♂️ 良い例:isを使用
if x is None:
print("実行される")
この場合は逆に is
を使うのが Python のイディオムです。
落とし穴 5:演算子の優先順位
and
、or
、not
の優先順位は、直感とは異なることがあります。
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
複雑な条件は、括弧を使って明示的に優先順位を示すことをおすすめします。
# 🙅♂️ ダメな例:優先順位があいまい
if a and b or c and d:
print("何が起こる?")
# 🙆♂️ 良い例:括弧で明示
if (a and b) or (c and d):
print("意図が明確")
落とし穴 6:カスタムクラスの真偽値評価
カスタムクラスを作る場合、真偽値評価の挙動を制御できます。
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__
メソッドを明示的に定義することをおすすめします。
# 🙆♂️ 良い例:明示的に__bool__を定義
class CustomContainer:
def __init__(self, items):
self.items = items
def __bool__(self):
return bool(self.items)
これにより、クラスのインスタンスの真偽値評価をカスタマイズできます。
まとめ
今回紹介した 7 つの落とし穴を整理すると、以下のようになります。
- 数値のゼロ判定は
==
を使う - 空白文字は空文字とは異なる
-
and/or
は最後に評価された値を返す -
None
との比較はis
を使う - 複雑な条件は括弧を使って明示する
- カスタムクラスは
__bool__
で挙動を制御できる
これらの落とし穴を知っておくことで、より安全な Python コードを書くことができます。
最後まで読んでいただき、ありがとうございました!
他にもアドベントカレンダー記事を書いています!
他にも、2024 年のアドベントカレンダーに参加しています。
以下の記事でまとめているので、よければ他の記事も読んでいただけると嬉しいです!