Python学習6日目～例外処理

## 例外処理
必ず必要な機能ではないが、例外処理があると例外として通知した上で通常の処理に戻ることが出来る

### isnumericメソッド
文字列が数字だけで構成されているのかチェックしてくれる
~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    price = input('price: ')
    if not price.isnumeric():
        print('Input an integer')
        print('-'*20)
        continue
    count = input('count: ')
    if not count.isnumeric():
        print('Input an integer')
        print('-'*20)
        continue
    price = int(price)
    count = int(count)
    if count == 0:
        print('The count must be != 0')
        print('-'*20)
        continue
    print('price//count=',price//count)
    print('-'*20)
~~~
~~~:実行結果
price: 3000
count: 2
price//count= 1500
--------------------
price: 1.23
Input an integer
--------------------
price: 3000
count: 3.4
Input an integer
--------------------
price: 4999
count: 0
The count must be != 0
--------------------
~~~

### try文とexcept節
try文は例外が発生する可能性のある処理を複数の文に渡って書ける
except節は例外処理の対象にしたい例外(例外クラス)を指定する

~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    try:
        price = int(input('price: '))
        count = int(input('count: '))
        print('price//count=',price//count)
    except ValueError:
        print('Input an integer')
    except ZeroDivisionError:
        print('The count must be != 0')
    print('-'*20)
~~~
~~~:実行結果
price: 1000
count: 2
price//count= 500
--------------------
price: 1.23
Input an integer
--------------------
price: 3000
count: 3.14
Input an integer
--------------------
price: 3000
count: 0
The count must be != 0
--------------------
~~~
めっちゃ短くなった

### except節の色んな書き方
#### 複数の例外を1つのexcept節にまとめる

~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    try:
        price = int(input('price: '))
        count = int(input('count: '))
        print('price//count=',price//count)
    except (ValueError,ZeroDivisionError):
        print('Error.')
    print('-'*20)
~~~
~~~:実行結果
price: 1.23
Error.
--------------------
price: 3000
count: 0
Error.
--------------------
~~~
except節にて複数の例外を指定すると更に短くできる

#### あらゆるエラーをまとめる

~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    try:
        price = int(input('price: '))
        count = int(input('count: '))
        print('price//count=',price//count)
    except:
        print('Error.')
    print('-'*20)
~~~

エラー内容を指定しないとあらゆるエラーを例外として処理してくれる
便利そうに見えて実は大きな欠陥がある
**KeyboardInterrupt**エラーも例外として処理してしまうため、上記のプログラムは終了できない
この場合、**Exception**という例外(例外クラス)を指定することでプログラムをきちんと終了できるようになる

~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    try:
        price = int(input('price: '))
        count = int(input('count: '))
        print('price//count=',price//count)
    except Exception:
        print('Error.')
    print('-'*20)
~~~
~~~:実行結果
price: 2000
count: 2
price//count= 1000
--------------------
price: 2000
count: 1.2
Error.
--------------------
price: 12.2
Error.
--------------------
price: 2000
count: 0
Error.
--------------------
~~~

これで良いように見えるが、実はよろしくない点が一つある
それが**例外の内容がわからない点**である
そこでExceptionの内容を変数eに格納することができる

~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    try:
        price = int(input('price: '))
        count = int(input('count: '))
        print('price//count=',price//count)
    except Exception as e:
        print(e)
    print('-'*20)
~~~
~~~:実行結果
price: 1.23
invalid literal for int() with base 10: '1.23' # 例外の内容を出力
--------------------
price: 1000
count: 0
integer division or modulo by zero # 例外の内容を出力
--------------------
~~~

### finally節
finally節は例外の発生に関わらず実行する処理を記載する

~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    try:
        price = int(input('price: '))
        count = int(input('count: '))
        print('price//count=',price//count)
    except Exception as e:
        print(e)
    finally:
        print('-'*20)
~~~
~~~:実行結果
price: 1000
count: 0
integer division or modulo by zero
--------------------
price: 1.23
invalid literal for int() with base 10: '1.23'
--------------------
price: -------------------- # finally節の処理
Traceback (most recent call last):
~~~

実はもとの実行結果と最後が異なる
~~~:実行結果
price: 1.23
invalid literal for int() with base 10: '1.23'
--------------------
price: 1000
count: 0
integer division or modulo by zero
--------------------
price: Traceback (most recent call last): # この部分がことなる
~~~

finally節を使うことでfinally節以降の処理が実行されたうえでプログラムが終了する

### else節
例外が発生しなかったときの処理
except節より後にfinally節より前に書く

~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    try:
        price = int(input('price: '))
        count = int(input('count: '))
        print('price//count=',price//count)
    except Exception as e:
        print(e)
    else:
        print('Perfect!')
    finally:
        print('-'*20)
~~~
~~~:実行結果
price: 1000
count: 2
price//count= 500
Perfect!
--------------------
~~~

### raise文
意図的に例外を発生させる

~~~Python:example.py
# 価格(price)と人数(count)を入力して価格を人数で割って整数で表示する
while True:
    try:
        price = int(input('price: '))
        if price < 0:
            raise Exception('The price must be >= 0')
        count = int(input('count: '))
        print('price//count=',price//count)
    except Exception as e:
        print(e)
    else:
        print('Perfect!')
    finally:
        print('-'*20)
~~~
~~~:実行結果
price: -1000
The price must be >= 0
--------------------
~~~

raise文には次の使い方もできる

* 処理中の例外を再送出
* 処理中の例外を原因とする別の例外を発生させる

~~~Python:example.py
try:
    1/0
except Exception as e:
    print(e)
~~~
~~~:実行結果
division by zero
~~~

これまでの書き方だとこんな感じ
再送出すると

~~~Python:raise.py
try:
    1/0
except Exception:
    raise
~~~
~~~:実行結果
例外が発生しました: ZeroDivisionError
division by zero
  File "C:\Codes\Python\raise.py", line 2, in <module>
    1/0
    ~^~
ZeroDivisionError: division by zero
~~~

except節で処理が完結せず、プログラムエラーで終了する

~~~Python:raise.py
try:
    1/0
except Exception as e:
    raise Exception from e
~~~
~~~:実行結果
例外が発生しました: Exception
division by zero
  File "C:\Codes\Python\raise.py", line 2, in <module>
    1/0
    ~^~
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

  File "C:\Codes\Python\raise.py", line 4, in <module>
    raise Exception from e
Exception: 
~~~

「ZeroDivisionErrorを原因にExceptionが発生した」というエラー内容になる