【Python】最初、混同してしまう「assert, except, raise」文について

# assert

**デバックを作業を容易に行うため** に使用されます

条件式が`False`の場合には`AssertionError`を発生させて停止。
`True`の場合はそのまま処理が続きます

```sample.py
assert "条件式をここに書く", "Falseの場合のメッセージ"
```

```sample.py
# 例）
# 1. AssertionErrorは発生せずに、処理は続く
assert "apple" == "apple", "りんごでは無いです"

# 2
assert "apple" == "orange", "りんごでは無いです"
# => AssertionError : りんごでは無いです
```

### では、if文と、assert文の違いは?

同じ様に条件分岐をするif分との違いは何なのか

```sample.py
if "apple" == "apple":
    is_same_fruit = True
else:
    is_same_fruit = False
```

if文は`True`でも`False`でも条件によって**処理を行う**のに対し、
assert文は`True`の場合は処理を行うが、`False`の場合は **`AsserttionError`を発生**させます

# except

exceptは、**例外処理を受け取ります。** また、**キャッチするとも言います**
想定外の処理が起きた時用の処理を用意します

`try文`と一緒に使用します

```sample.py
try:
    エラーが発生する可能性がある処理
except:
    例外発生時の処理
```

## こんな使い方がある

### ①未定義の変数の例外をキャッチする
未定義の変数は組み込み例外クラスの`NameError`が発生します

```sample.py
try:
    print(apple)
except ValueError as e:
    print("ValueError をキャッチ")
    print(e)
except NameError as e:
    print(("NameErrorをキャッチ"))
    print(e)

# => NameErrorをキャッチ
# => name 'apple' is not defined
```

`try`にエラーが発生する可能性がある処理を書いています
仮にエラーが発生した場合、`except`で例外をキャッチ出来るようになっています
上記の例では、未定義の変数が参照されようとしたので例外クラスである`NameError`が発生し、`except NameError as e:`でキャッチしています

仮に、`try/except`を書かない場合

```sample.py
print(apple)

# =>  print(apple)
# NameError: name 'apple' is not defined
```

キャッチするのではなく、例外である`NameError`が発生して処理が終了します

>hogehoge：「そもそも、なぜ例外処理をキャッチする意味がある？」
hogehoge：「エラーとして、スタックトレースに出力されるんなら同じ事では？」

理由については後ほど記載。

### ② 例外オブジェクトを取得する

①のサンプルコードにもありましたが、
`except 例外クラス as 変数:`とすることで、例外オブジェクトを取得することが出来ます

```sample.py
try:
    print(apple)
except NameError as e:
    print(e)
    print(type(e))
except:
    print("それ以外の例外")

# => name 'apple' is not defined
# => <class 'NameError'>
```

## `except`における疑問の解消

>hogehoge：「そもそも、なぜ例外処理を発生させる意味がある？」
hogehoge：「エラーとして、スタックトレースに出力されるんなら同じ事では？」

* **該当の処理だけを止めるため**
  * 例外が起きると、処理が止まるが全体の処理が止まるわけではなく、例外の該当の箇所だけで収まる。
* **エラー原因の解析のため**
  * エラーが発生する可能性のある箇所で、意図的に例外としてキャッチすることで、解析しやすいようにエラーメッセージを添えることが出来る
* **ソフトランディングさせるため**
    * その後の処理で誤ったデータが登録されないように
    * DBをロールバックさせるため

https://teratail.com/questions/28235

>hogehoge：「`if else`でも代替えできるんじゃ？」

**処理自体は可能。**
だが、**全てのエラーを予測して**例外をキャッチしなければなりません
よって、`try/except文`を使います

# raise

**例外を意図的に発生させたい場合に使用**します
例外が発生する可能性のある箇所に書きます
`raise`で例外を発生させて、`except`でキャッチしたりします（後ほど、詳しく説明します）

```sample.py
raise 例外クラス（メッセージ）

# 例
raise RuntimeError("Something bad happened")
```

エラーが発生するとこんな感じでスタックトレースに出ます

```
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened
```

また、

* 独自で例外クラスを作る
* Exceptionクラスを継承したクラスを作る

なども可能です。

## 例えばこんな挙動になる

### ●＜シンプルに、例外を発生させて、該当する例外をキャッチ＞

`raise NameError`で例外を発生させて、例外が検出された時、
該当の例外クラスの`except`句が実行されます

```sample.py
try:
    raise NameError("変数が定義されていません")
except NameError as e:
    print(e)

# => 変数が定義されていません
```

### ●＜例外を発生させて、該当する例外とは異なるのでキャッチ出来ない＞

`NameError`を発生させていますが、`except`句では`ValueError`を待っているのでキャッチ出来ません

`except`句内の出力が確認できないことが分かります

しかし、例外はスタックトレースへエラーメッセージと共に送出されています

```sample.py
try:
    raise NameError("変数が定義されていません")
except ValueError as e:
    print(e)
    print("例外をキャッチ！")

# Traceback (most recent call last):
#   File "Main.py", line 5, in <module>
#     raise NameError("変数が定義されていません")
# NameError: 変数が定義されていません
```

> hogehoge：「結局のところ一番知りたいのは、exceptとraiseの違いは細かな違いって何？」

## except と raiseの違い

公式にも記載があります

><font color="red">例外が発生したかどうかを判定したいだけで、その例外を処理するつもりがなければ、単純な形式の raise 文を使って例外を再送出させることができます</font>
https://docs.python.org/ja/3/tutorial/errors.html#raising-exceptions

つまりどういうことかというと、
* **例外として、エラーメッセージを送出したいだけとかなら`raise`でOK**
* **例外が発生した後、処理を行いたい場合に`except`でキャッチする**
    * エラーメッセージをdictへ整形したり、エラーログに残したり、csv出力したり とか

## 具体例を説明すると、

ここからは、コードをいくつか例を用いて説明します
（理解できている方は飛ばしてください）

### ●＜raiseでエラーメッセージを送出したいだけ＞

```sample.py
raise NameError("変数が定義されていません")

# Traceback (most recent call last):
#   File "Main.py", line 1, in <module>
#     raise NameError("変数が定義されていません")
# NameError: 変数が定義されていません
```

### ●＜例外を発生させて、except句内で処理する＞

`Exception`クラスを継承している独自で作成した`SampleException`を使って説明します。

条件分岐を行なって例外を発生させます

`except句`では、`SampleException`をキャッチして、`except句`内の処理が行われます

```sample.py
class SampleException(Exception):
    pass

def cal():
    try:
        a = 10
        if a % 4 == 0:
            raise SampleException("10は4で割り切れてしまいました")
        elif a % 2 == 0: # 当条件に当てはまり例外が発生
            raise SampleException("10は2で割り切れてしまいました")
        elif a % 5 == 0:
            raise SampleException("10は5で割り切れてしまいました")
    except SampleException as e:
        print(e)
        print("例外キャッチ")
        print("ここに処理を書く")

cal()

# 10は2で割り切れてしまいました
# 例外キャッチ
# ここに処理を書く
```

## `raise`を使用する場面とは？

* 致命的なエラーが発生する可能性のある時
* 自分が作った処理にエラーが発生した際に、それを外部に伝えるため

以下記事が、分かりやすいです

https://python.ms/raise/#_1-%E5%85%B7%E4%BD%93%E4%BE%8B%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%BF%E3%82%8B%E3%80%82

# 参考

https://docs.python.org/ja/3/reference/simple_stmts.html

https://docs.python.org/ja/3/tutorial/errors.html

https://tutorialmore.com/questions-493707.htm