and/orを使って複数条件で判定したらおかしな結果になった(Python)

orを使って条件判定したらおかしなことになった,あるある話.環境は3.7です.

あれ? そなたはNoneTypeではなかったか...

あるオブジェクトの型がlist, tuple, setのいずれかであるかを判定したかったので,次のようなコードを書いた.

def check(obj):
    if type(obj) is list or tuple or set:  # 型判定(のつもり)
        print('OK!')
    else:
        print('NG...')

引数のobjNoneが入っていたのだけれど,なんとシーケンス型だと言うではないか!

>>> check(None)
OK!

お,おう...

評価の結果は...

andやorの後に続くのは条件式であり,たとえオブジェクトであったとしてもTrueもしくはFalseのどちらかとして評価されてしまう. つまり上のコードでは,type(None) is listtuplesetのいずれかがTrueであれば,ifブロックが実行されることになる.

TrueもしくはFalseのどちらに評価されるかはbool()を使って調べることができる.

>>> bool(type(None) is list)
False
>>> bool(tuple)
True
>>> bool(set)
True

つまり,常にifブロックが実行されてしまうことがわかった.
if tupleとかで条件式が成立してしまうんだね...何気に怖い.

正しい判定コード

というわけで,書き直したコードがこちら.

def check(obj):
    if type(obj) is list or type(obj) is tuple or type(obj) is set:
        print('OK!')
    else:
        print('NG...')

>>> check_sequence(None)
NG...

>>> check([])
OK!

>>> check((1,))
OK!

まとめ:and/orのあとは条件式

はじめはわかっているつもりでも「なんかこれでいけるんじゃない?」「むしろPythonistaっぽくない??」 という謎のPythonならありえそう」感が出てきてしまってやらかしてしまった. 気をつけませう.