Posted at

Python の or と and 演算子の罠

More than 3 years have passed since last update.

普通、プログラム言語ではorand (または||&&などの類似表現) は (2項の) 論理演算を意味します。

つまりorandが返す値は bool 値 (True/False) です。

C系の言語では True/False が 1/0 で代用されたりもしますが、とにかく論理演算 or と and は論理値に相当する値を返します。

しかし、Python では、orandは論理値を返すとは限りません。

Pythonにおいて、式x and yは、次と等価です:

if not bool(x):  # xの論理値がFalseなら

return x # yを見ることなく、しかもFalseではなくxそのものをリターン
else: # xの論理値がTrueなら
return y # bool(y) ではなく、yそのものをリターン

同様に、式x or yは次と等価です:

if bool(x):  # xの論理値がTrueなら

return x # yを見ることなく、しかもTrueではなくxそのものをリターン
else: # xの論理値がFalseなら
return y # bool(y) ではなく、yそのものをリターン

Pythonでは、長さ0の文字列の論理値がFalse、長さ1以上の文字列の論理値がTrueであることに気をつけると、次のようにまとめることができます:

'123' or '456'  -> '123'  # bool('123') がTrueなので、 左辺値'123'をリターン

'123' or '' -> '123' # bool('123') がTrueなので、 左辺値'123'をリターン
'' or '456' -> '456' # bool('') がFalseなので、右辺値'456'をリターン
'' or '' -> '' # bool('') がFalseなので、右辺値'' をリターン
'123' and '456' -> '456' # bool('123') がTrueなので、 右辺値'456'をリターン
'123' and '' -> '' # bool('123') がTrueなので、 右辺値'' をリターン
'' and '456' -> '' # bool('') がFalseなので、左辺値'' をリターン
'' and '' -> '' # bool('') がFalseなので、左辺値'' をリターン

上の例では、orandの結果であるにも関わらず、True/Falseは一切返ってきません

もしもPythonで2項論理演算子 (orとand) から返ってくる値が常に論理値True/Falseであることを保証したい場合は、bool(x) or bool(y)またはbool(x) and bool(y)とする必要があります (bool(x or y), bool(x and y) でも良いですが)。

普通、xyとして論理値True/False以外の値を渡すことはないですが、自動的にbool(x)が評価されるため、たまに忘れることがあります。

なんでこんなことになっているのかというと、よく言われているのは、Noneの扱いが便利になるからだそうです。

PythonではNoneの論理値はFalseです (bool(None) -> False) ので、次のコード

# x には None が格納されているのかどうかわからない

if x is None:
x = '456'
# x = '456' if x is None とも書けるが個人的に好きじゃない

は、x = x or '456'で代用できます

ただし、等価ではありませんxに 空文字列''や 空リスト[]が格納されていた時の振る舞いが異なります。

x''[]が格納されていた場合、x is Nonebool(x)Falseです。よって、 is Noneを用いたコードの場合xは再代入されませんが、

x or '456'を用いたコードの場合xには'456'が再代入されます。


待て、 if x or y: はどうなる?

もしもorが論理値を返さないとするならば、ifで今までx or yを使っていたのは偶然うまくいっていたのか?

いえ、それは偶然ではなく必然的にうまくいっていました。

Pythonでは、ifの条件式は勝手に真理値が判別されます。つまり、if bool(x or y): と同じことが勝手におこなわれています。