Pythonのifの条件式にはboolean(bool型)を書く必要があります。ところが、一見、booleanではないものが指定された場合どうなるのでしょうか?この記事では、その挙動について詳しく見ていきます。
目次
boolean context
ifの条件式にはbool型を書きますが、bool型ではないものが条件式に指定された場合、これをbool型に変換しなくてはなりません。
このとき、Pythonでは内部的には、bool( )という組み込み関数を通じてbool型に変換されます。(変換しようとします)
bool()関数は、引数で渡されたのがNoneであれば、これをFalseとして扱います。
print(bool(None))
# False
また、bool()関数は、引数で渡されたのがオブジェクトである場合、そのオブジェクトの__bool__()メソッドがあれば、これを呼び出そうとします。
つまり、次の例では、xはFalseとして扱われます。
class X:
def __bool__(self)->bool:
return False
x = X()
if x:
print("Truthy")
else:
print("Falsy")
# Falsy
thuthyとfalsy
booleanを要求するコンテキストに出現したときにFalseとして扱われる値のことを JavaScriptの用語で、falsy(falseっぽい?)、Trueになる値のことをtruthy(trueっぽい?)と呼びます。
これらは、False/Trueという概念を拡張するときに使われます。さきほどの例であれば、(オブジェクトを指す)変数がNoneであればfalsy、Noneでなければtruthyです。しかし、そのオブジェクトが__bool__()メソッドを持っているなら、このメソッドの返し値によってflasyかtruthyかが決まります。
空のsequenceはFalsy
str(文字列型)やlist型は、sequenceです。Pythonでは空のsequenceはfalsyです。
s = ""
if s:
print("Truthy")
else:
print("Falsy")
# Falsy
これはどのような仕組みになっているかと言うと、sequenceは、__len__()メソッドを実装することになっています。つまり、暗黙実装された__bool__()メソッドは__len__()メソッドを呼び出してtruthyかfalsyかを判定しているということです。
# このような__bool__()が暗黙的に実装されていると考えられる。
def __bool__(self)->bool:
return __len__() != 0
__len__()メソッドだけを実装したクラスでこのことを確認しましょう。
class X:
def __len__(self)->int:
print("__len__")
return 1
x = X()
print(bool(x)) # ここで__bool__()メソッドから__len__()が呼び出される
if x: # ここでも__bool__()メソッドから__len__()が呼び出される。
print("Truthy")
else:
print("Falsy")
# __len__
# True
# __len__
# Thuthy
確かに__len__()メソッドが呼び出されています。
sequenceがNoneか空である判定
Noneもfalsyなので、C#などでよくある、nullか””(空の文字列)であるかの判定である、if ( ! s.IsNullOrEmpty() ) は、単に if s : と書けます。
s = None
print(bool(s))
# False
s = ""
print(bool(s))
# False
s = "ABC"
print(bool(s))
ユーザー定義型がtruthyであるかfalsyであるかの判定
以上のことから、ユーザー定義型についてそれがtruthyであるか、falsyであるかは次の組み合わせがあることがわかりました。
オブジェクト変数がNoneである→falsy
オブジェクト変数がNoneではないなら以下の分類になります。
__bool__ | __len__ | truthy or falsy ? |
定義されていない | 定義されていない | truthy |
定義されていない | 定義されている | __len__()が非0を返したときtruthyさもなくばfalsy |
定義されている | どちらでも | __bool__()の返し値そのまま。 ※ __len__()が呼び出されるかどうかは__bool__()の実装次第。 |