pythonの真偽値と条件分岐

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

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

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

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

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

[PEP8プログラミングに関する推奨事項](https://pep8-ja.readthedocs.io/ja/latest/#id42)にはこのように書かれています。

> 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`)した条件分岐ができます。

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

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

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

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

具体例は後述します。

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

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

1行で書いたり、

```python:例
print("Is True" if "" else "Is False")  # Is False
```

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

```python:例
res = True if [] else False
print(res)  # False
```

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

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

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

```python:例
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: 空配列でない時と空配列の時で処理を書き分ける

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

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

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

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

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

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

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

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

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

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