ifの条件式のなかでandやorを使ったことがあると思いますが、これはPythonではどのような仕組みで実現されているのでしょうか?この記事ではその仕組みを詳しく見ていきます。
ブール演算子 and/or
Pythonのブール演算のand/orは次のようになっています。(公式ドキュメントより)
演算 結果 注釈 x or y
x が偽なら y , そうでなければ x (1) x and y
x が偽なら x , そうでなければ y (2) not x
x が偽なら True
, そうでなければ False
(3)
注釈:
この演算子は短絡評価されます。つまり第一引数が偽のときにのみ、第二引数が評価されます。 この演算子は短絡評価されます。つまり第一引数が真のときにのみ、第二引数が評価されます。 not
は非ブール演算子よりも優先度が低いので、 not a == b
は not (a == b)
と解釈され、 a == not b
は構文エラーです。
それぞれを詳しく見ていきましょう。
短絡評価とは
y = False and X()
のような式があった場合、andの左側がFalseなので、andの右側がTrueであろうがFalseであろうが右辺はFalseと確定します。それゆえ、andの左側がFalseであると確定した時点でandの右側に対しては何も調べる必要がないのです。このような評価の仕方を短絡評価と呼びます。
orについても同様で、
y = True or X()
のような場合、orの左側がTrueであると判明した時点で右辺はTrueと確定します。X()は評価(実行)されません。
ブール演算子 or
x or y の形で用います。
さきほどの説明には、「第一引数が偽のときのみ、第二引数が評価されます」とあります。
つまり、第一引数の真偽を判定せねばならないので、第一引数がtruthyかfalsyか判定するためにbool型への変換が行われます。これは、bool()関数によって行われます → Pythonのtruthyとfalsyについて
bool関数は、オブジェクトである場合、__bool__()メソッドを呼び出します。(これがわからない人は、上のリンク先の記事を確認してください。)
つまり、第一引数がオブジェクトである場合、その__bool__()メソッドが呼び出されます。その返し値がTrueである場合、or全体がTrueであることが確定するので第二引数については評価しません。(何もしません。)
第一引数に対して__bool__()メソッドを呼び出した返し値がFalseである場合、第二引数について調べなければなりません。しかし、このとき、第二引数に関しては、bool()関数を呼び出さずに、単に評価した結果をor全体の値となります。
x = False or 123
print(x)
# 123
// Trueではないことに注意。
// 123がそのまま右辺の値となりxに代入される。
このことは、次のようなコードで確かめられます。
class C:
def __init__(self, s:str):
self.s = s
def __bool__(self) -> bool:
print(f"__bool__ {self.s}")
return False
def __str__(self)->str:
return str(self.s)
x = C("X")
y = C("Y")
z = x or y
print(z)
# __bool__ X
# Y
// y.__bool__()は呼び出されていない。
// また、zにはyがそのまま代入されていることがわかる。
ブール演算子 and
andに関しても同様で、第一引数をboolに変換し、それがTrueである場合にのみ第二引数を評価します。そのときの値がand全体の値となります。
print(True and 123)
# 123
print(234 and 345)
# 345
短絡評価を用いたデフォルト値の適用
Web系の開発で、str型の値がNoneか””(空の文字列)であれば、デフォルト値を代入させたいことがよくあります。このとき、orの短絡評価を用いて次のように書けます。
s = request.form.get("USER_INPUT") or "DEFAULT"
// get()の返し値がNoneか""であればfalsyなので、
// orの第二引数が評価され、その値がそのままsに代入される。
評価とは?
ここまでに何度か評価と言う用語が出てきましたが、そのまま値を代入しているのとは何が違うのでしょうか?
評価とは、eval()関数だと思うと良いでしょう。インタラクティブモードなどで実行したときの結果と同じです。
つまり、次のような挙動になります。
print(eval("123"))
# 123
// 数値やオブジェクトの場合、それがそのまま値となる
print(eval("1+2"))
# 式の場合、その式の計算結果がその値となる
def f():
return 234
print(eval("f()"))
# 234
// 関数呼び出しの場合、関数を実行した返し値がその値となる
「評価」と書かれていればeval()と同等の処理と覚えておけば良いですね。
def f():
return 123
print(True and f()+3)
# 126
// andの第一引数がTrueなので第二引数が評価される。
// f() + 3なので、eval("f()+3")のような処理がなされる。