pythonの真偽値と条件分岐

こんにちは、ピリカの冨田です。

pythonで、Noneかどうか、空文字列かどうか、空配列かどうか、空辞書かどうかの判定とその時の処理の書き分けで迷いがちの冨田です。

それらを正しく書き分けるためには、何がNoneで何がTrue/Falseで、それらがどうやってやif文や比較演算子で処理されるのかをちゃんと理解する必要があると思っています。

そこで、ここでは、python3の真偽判定とその時の条件分岐について書こうと思います。

この記事を書こうと思った訳

条件式は色々な書き方ができますが、推奨されている書き方や短く簡潔に書く方法があります。

PEP8プログラミングに関する推奨事項にはこのように書かれています。

None のようなシングルトンと比較をする場合は、常に is か is not を使うべきです。絶対に等値演算子を使わないでください。

また、 本当は if x is not None と書いているつもりで、 if x と書いている場合は注意してください - たとえば、デフォルトの値がNoneになる変数や引数に、何かしら別の値が設定されているかどうかをテストする場合です。この「別の値」は、ブール型のコンテクストでは False と評価される(コンテナのような)型かもしれませんよ!

not ... is ... ではなく、 is not 演算子を使いましょう。これらは機能的に同じですが、後者の方が読みやすく、好ましいです。:

これらは真偽判定が大きく関わっているのですが、「なんでだっけ?」と思っていて、ちゃんと整理したいなと思っていました。

真偽値一覧

まず、pythonはどのオブジェクトも真(True)もしくは偽(False)を持ちます。

下記はその一覧になります。

(それぞれのオブジェクトをBool型に変換するメソッドbool()を使うと確認できます)

type value bool(<value>)
bool True True
bool False False
None None False
int 0 False
int 1 True
str "" False
str "HelloWorld" True
list [] False
list [1, 2, 3] True
list [0] True
list [""] True
dict {} False
dict { "name" : "Taro" } True
dict {0} True
dict {""} True

真偽判定で条件分岐する

上記の真偽判定を利用して、if文で、条件式を記述(e.g. if A is [])せずに、値を記述(e.g. if A)した条件分岐ができます。

def function(val: Any) -> None:
    if val:
        # valがTrueであるときの処理
    else:
        # valがFalseであるときの処理

また、if notで、逆の条件式、つまり、条件式・値がFalseであるときに処理を実行することもできます。

def function(val: Any) -> None:
    if not val:
        # valがFalseであるときの処理
    else:
        # valがTrueであるときの処理    

基本的には、これらを利用して、空文字列、空配列、空辞書の判定をして処理を書き分けることができます。

具体例は後述します。

(余談) 分岐処理・真偽判定をもっと簡単に書く

余談ですが、if文について、

1行で書いたり、

print("Is True" if "" else "Is False")  # Is False

結果を変数に代入したりできます。

res = True if [] else False
print(res)  # False

また、ある値の真偽を返すだけであれば、

def funcion(val: Any) -> bool:
   return True if val else False

とせずにに、bool()を使って

def function(val: Any) -> bool:
    return bool(val)

と書くこともできます。

if not <value>if <value> is None

if not <value>と似ている(?)処理に、if <value> is Noneがあります。

まず、isは同一のオブジェクトであるかを判定する比較演算子です。 なので、if <value> is Noneは、値がNoneであるかのみを判定しています。

それに対して、if not <value>は、値が偽であるかを判定します。 つまり、bool(<value>)Falseになるものかどうかを判定しています。 なので、Noneであるかどうかだけではなく偽であるもの(例えば、0や空配列)かどうかも判定します。

それを踏まえて、私のよくある使い分け方は、

  • ある値がNoneであるか判定する時: if <value> is None

  • ある値が存在するかどうか判定する時: if not <value>

PEP8に書かれている、

また、 本当は if x is not None と書いているつもりで、 if x と書いている場合は注意してください - たとえば、デフォルトの値がNoneになる変数や引数に、何かしら別の値が設定されているかどうかをテストする場合です。この「別の値」は、ブール型のコンテクストでは False と評価される(コンテナのような)型かもしれませんよ!

は、これが理由でした。

なぜ== Noneではなくis Noneなのか

最初の頃、== None とかいたらエディタに怒られてなんでだろうと思っていました。

==isは違いがあって、

  • ==: 等価(同じ値)であるかどうか

  • is: オブジェクトID(id())が同一であるかどうか。

です。

pythonはオブジェクト毎に異なるIDが付与されます。 (このオブジェクト毎に異なるIDというのもなかなか手強いところなので、別途まとめるとします)

そのIDが一致しているかどうかを判定するのかisです。

また、isの方が処理が早く、オーバーライドできません。

そのため、ある値がNoneであるかどうかを判定するには、<value> == Noneではなく<value> is Noneと書きます。

PEP8に書かれている、

None のようなシングルトンと比較をする場合は、常に is か is not を使うべきです。絶対に等値演算子を使わないでください。

は、これが理由でした。

(例) 配列の例: 配列と空配列とNone

では、これらを踏まえて、配列の処理を考えてみます。

値のある配列、空配列、Noneについて考えます。

例1: 空配列でない時と空配列の時で処理を書き分ける

def function(l: list) -> None:
    if  l:
        # 空配列でないときの処理
    else:
        # 空配列であるときの処理

例2: 空配列を先に処理したい時

def function(l: list) -> None:
    if  not l:
        # 空配列でであるときの処理
    else:
        # 空配列でないときの処理

例3: さらにNone判定も加えたい時

def function(l: Optional[list]) -> None:
    if l is None:
        # Noneであるときの処理
    if  not l:
        # 空配列でであるときの処理
    else:
        # 空配列でないときの処理

例: 空配列の判定をする(Noneもしくは空配列の時はFalse、それ以外の時はTrueを返す)

def function(l: Optional[List]) -> bool:
    return bool(l)

例: 空配列の時はNoneを、それ以外の時は配列を返す

def function(l: list) -> Optional[list]:
    return l if l else None

今回は、is==のところを端折ってしまったので、今度ここもちゃんとまとめたいと思います。