無限ループにハマった
Pythonでコードを書いていて無限ループにハマりました。
問題
以下、その時のコードのシミュレーションです。
import random
def _randomReturnArgOrZero(a):
"""ランダムに引数をそのまま返したり0を返したりする"""
if bool(random.getrandbits(1)):
return 0
return a
def main():
x = None
while True:
x = _randomReturnArgOrZero(x)
if x:
print('the argument is not returned; end.')
break
else:
print('just the argument is returned; retry.')
return x
ナンセンスな例ですが、こんな感じのことをやろうとしました。はい、無限ループです。
Pythonists な各位にはお分かりでしょうが、 if x
は絶対に通らない節になっています。
Pythonでは0は偽として扱われる
ということです。単純な話でしたが、実際のコードの中ではなかなか気づけませんでした。
シェルで0が真、0以外が偽として扱われるのとは反対ですね。
シェルで0が真を表すのは、成功を意味するステータスコードとして0が利用されているというのはそうなんですが、突き詰めていくと、何も返さないことで成功を表すというUnix哲学から来ている気がします。
ちなみにCommon Lisp では nil
および空リスト ()
のみが偽で、それ以外は真(明示的に真の値を表現する場合はシンボル t
を使う)です。つまり、0も1も、空文字列 ""
も真です。
これはLispが"List Processor"であることから来ている性格ですね。
なお、Cでは0が偽として扱われますが、これは「Cがもともとビット処理言語だったことを意味している」(『実践Common Lisp』 p.47 オーム社、Peter Seibel 著、佐野匡俊・水丸淳・園城雅之・金子祐介 共訳)ということです。
rubyだとnilとfalseのみが偽です。
この辺り、各言語の性格が現れていて非常に面白いです。
Pythonにおける真偽値
では、Pythonではどうでしょうか。
0が偽として扱われるのは冒頭で見た通りですが、空文字列や空リストは?
ということで、いくつか見ていきます。
>>> bool(False)
False
>>> bool(True)
True
>>> bool(0)
False
>>> bool(1)
True
>>> bool(0.0)
False
>>> bool("") # 空文字列
False
>>> bool([]) # 空リスト
False
>>> bool({}) # 空辞書
False
>>> bool(()) # 空タプル
False
>>> bool(set()) # 空セット
False
>>> bool(None) # None型
False
Pythonの場合は空のオブジェクトはすべて偽、という扱いですね。
シンプルでわかりやすく、を目指す言語的性格の表れなのでしょうか。
Perlとは少し似ている気がします。言語の登場時期が近いのも関係があるかもしれません。
Cf.) Perl の真偽値について
結論
xにbool型の値以外が入ってくる可能性があるところで、安易に if x
みたいなコードを書くな、ということですね。
挙動が完全に理解できているなら良いですが、そうでなければ if x is not None
みたいにちゃんと比較演算子を使ってあげるべきです。
最初の例についての補足
最初の例で bool(random.getrandbits(1))
でランダムにif文を通す処理を書いていましたが、まさにこれは0/1をboolで変換して真偽値を作っていました。これを書ければ if 0
が決して通過しない条件節であることは分かるはずです。
ただ、実際のコードではランダムに真偽値を作るということは行なっていなかったので、もちろんそんなことには気づかなかったわけです。
ナンセンスな例を出してしまったので、ちょっと補足でした。