今回は Python の条件分岐と真偽値周りの話について。 ざっくりと内容をまとめると次の通り。
- Python の条件分岐には真偽値以外のオブジェクトを渡せる
- 意味的には組み込み関数
bool()
にオブジェクトを渡すのと等価になる
- 意味的には組み込み関数
- ただし条件分岐に真偽値以外のオブジェクトを渡すと不具合を生みやすい
- そのため、条件分岐には真偽値だけを渡すようにした方が良い
- なお、オブジェクトを
bool()
に渡したときの振る舞いはオーバーライドできる- 特殊メソッド
__bool__()
を実装すれば良い
- 特殊メソッド
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.4 BuildVersion: 18E226 $ python -V Python 3.7.3
下準備
今回の説明は Python の REPL を使って進めていくので、あらかじめ起動しておく。
$ python
Python の条件分岐について
Python の条件分岐には真偽値 (bool ) 型以外のオブジェクトも渡せる。
例えば、次のコードは Python においてちゃんと動作する。
関数 f()
における引数 x
は bool 型である必要もない。
>>> def f(x): ... # 引数が有効か無効かを判断するつもりの条件分岐 ... if x: ... print('Valid') ... else: ... print('Invalid') ...
では、上記の引数 x
に色々なオブジェクトを渡すと、どのように振る舞うだろうか。
ちょっと見てみよう。
例えば真偽値型の True
を渡してみる。
これは、当然ながら上のコードブロックに遷移する。
>>> f(True)
Valid
では、長さのある文字列だったら? これも、上のコードブロックに遷移する。
>>> f('Hello, World!')
Valid
非 0 の整数なら? これまた同様。
>>> f(1)
Valid
では、続いて None
を渡してみよう。
>>> f(None)
Invalid
この場合は、下のコードブロックに遷移した。 なんとなく、ここまでは直感どおりに思える。
じゃあ長さのない文字列 (空文字) を渡したらどうなるだろう。
>>> f('')
Invalid
なんと、この場合は下のコードブロックに遷移してしまった。
整数としてゼロを渡した場合も同様。
>>> f(0)
Invalid
では、上記の不思議な振る舞いは一体何に由来するものだろうか。
実はオブジェクトを条件分岐に渡すとき、意味的には組み込み関数 bool()
に渡すのと等価になる。
つまり、最初に示した関数 f()
は、次のコードと等価ということになる。
>>> def f(x): ... # オブジェクトの真偽値表現を組み込み関数 bool() で取得する ... if bool(x): ... print('Valid') ... else: ... print('Invalid') ...
組み込み関数 bool()
では、オブジェクトを真偽値として評価した場合の結果が得られる。
先ほど試したいくつかのオブジェクトを実際に渡してみよう。
>>> bool('') False >>> bool(' ') True >>> bool(0) False >>> bool(1) True
上記で得られる返り値の内容は、先ほどの検証で得られた振る舞いと一致する。
このように、真偽値以外のオブジェクトを条件分岐に渡すと直感的でない振る舞いをすることがある。 コードの直感的でない振る舞いは不具合につながる。 また、コメントでもない限り、意図してそのコードにしているのかも分かりにくい。
PEP20: Zen of Python にある Explicit is better than implicit.
を実践するのであれば、真偽値を渡すほうが良いと思う。
例えば、最初のコードで仮に「None
か否か」を判定したいのであれば、次のようにした方が良いと考えられる。
>>> def f(x): ... # オブジェクトが None か判定結果を真偽値として得る ... if x is not None: ... print('Valid') ... else: ... print('Invalid') ...
... is not None
は対象が None
かそうでないかを真偽値で返すことになる。
解釈にブレが生じることはない。
>>> 'Hello, World!' is not None True >>> '' is not None True >>> 1 is not None True >>> 0 is not None True >>> None is not None False
ちなみに、自分で定義したクラスのインスタンスが組み込み関数 bool()
に渡されたときの振る舞いはオーバーライドできる。
具体的には特殊メソッド __bool__()
を実装すれば良い。
以下のサンプルコードでは、クラス FizzBuzz
に特殊メソッドを定義して振る舞いをオーバーライドしている。
このクラスのインスタンスは渡された整数の値によって組み込み関数 bool()
から得られる結果を切り替える。
>>> class FizzBuzz(object): ... """整数が 3 か 5 で割り切れる値か真偽値で確認できるクラス""" ... def __init__(self, n): ... self.n = n ... def __bool__(self): ... # Fizz ... if self.n % 3 == 0: ... return True ... # Buzz ... if self.n % 5 == 0: ... return True ... # Others ... return False ...
引数が 3
か 5
で割り切れるときに True
を返し、それ以外は False
になる。
>>> o = FizzBuzz(3) >>> bool(o) True >>> o = FizzBuzz(5) >>> bool(o) True >>> o = FizzBuzz(4) >>> bool(o) False
いじょう。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る