Python基礎講座(11 例外)

##例外とは？
コードに間違いがあり、プログラムを[コンパイル](http://e-words.jp/w/E382B3E383B3E38391E382A4E383AB.html)した際にエラーが発生することを「コンパイルエラー」と言いますが、
コンパイルは正常に終了しても、その後実行中に何らかの異常が発生することを[例外](http://e-words.jp/w/E4BE8BE5A496.html)と言います。
自分の記事の中ではこれまで意味が伝わるように「エラー｣という言葉で「例外」を表現していましたが、
今後は正しい単語である「例外」を使用していきます。

これまでの記事で紹介した例外は以下のようなものがありました。

* 数値型と文字列型を+で結合する
* リスト.remove(要素)でリスト内に存在しない要素を削除しようとする
* ディクショナリでget(キー)を用いずに、存在しないキーを指定する

他にも有名な例外を発生させるコードが、数値を0で割るゼロ除算です。

```py3:division_by_zero.py
a = 10 / 0
print("{0}".format(a))
```

上記プログラムはコンパイル時にはエラーを発生させませんが、実行時に以下のように例外を発生させます。

**ZeroDivisionError: integer division or modulo by zero**

例外を発生しうるコードの箇所に「もし例外が発生したら～」と記述することを例外処理と言います。

## 例外の捕捉
プログラムが実行中に突然終了してしまっては困ります。
そこで、上に書いたゼロ除算のプログラムを、実行時に例外を発生させないよう修正します。

```py3:division_by_zero2.py
try:
    a = 10 / 0
    print("{0}".format(a))
except ZeroDivisionError:
    print("ZeroDivisionError!!")
```

例外を発生しうるコードを**try:**で括ります。そして例外が発生した場合の処理を**except *例外の種類* :**で括ります。
「例外の種類」は適切なものを記述しなければなりません。
例えば上記のプログラムでZeroDivisionError(=ゼロ除算例外)ではなく、
以下のようにValueError(=変数の型に合わない値が格納された例外)を指定すると、例外を捕捉しません。

```py3:division_by_zero_EXCEPTION2.py
try:
    a = 10 / 0
    print("{0}".format(a))
except ValueError:
    print("ZeroDivisionError!!")
```

上記のプログラムを実行すると例外が発生します。

## 例外に関する情報の利用
先ほどのプログラムでは例外が発生した場合、自分で出力するメッセージを作成していましたが、
Pythonでは例外発生時に情報を保持しているため、これを利用できます。
以下のプログラムを実行してみてください。

```py3.division_by_zero_info.py
try:
    a = 10 / 0
    print("{0}".format(a))
except ZeroDivisionError as e:
    print("type:{0}".format(type(e)))
    print("args:{0}".format(e.args))
    print("message:{0}".format(e.message))
    print("{0}".format(e))
```

**exception *例外の種類* as *変数* :** と記述すると、エラーの情報を持つ変数を定義できます。
出力結果は以下のようになります。自分で作成したメッセージの代わりにこれらを出力しても良いでしょう。

**type:\<type 'exceptions.ZeroDivisionError'\>
args:('integer division or modulo by zero',)
message:integer division or modulo by zero
integer division or modulo by zero**

## 複数の例外を捕捉する
先ほど書いたように、「例外の種類」は適切なものを記述しなければなりませんが、複数パターンの例外が
発生する場合は、分岐のelif同様に複数書くことが出来ます。

```
try:
	f = open(file_name,'w')
	data = dict_input['data']
	f.write(data)
	f.close()
except KeyError:
	print('キーが見つかりませんでした')
except (FileNotFoundError, TypeError) :
	print('ファイルが開けませんでした')
except:
	print('何らかのエラーが発生')
```
これはファイルをオープンするプログラムの処理の一部ですが、ファイルの入出力は
『Python基礎講座』では説明しないため、細かいコードの説明は省きます。
見て欲しいのは**except KeyError**、**except (FileNotFoundError,TypeError)**、**except** の部分です。
try句の中で例外が発生した場合、Pythonは初めにその例外がKeyErrorで補足出来るかを確認します。
補足できれば「キーが見つかりませんでした」と出力します。
KeyErrorで捕捉出来なかった場合、FileNotFoundErrorまたはTypeErrorで捕捉出来るかを確認します。
補足できれば「ファイルが開けませんでした」と出力します。
このように複数の例外に対して同一の処理を行うことが可能です。
それでも捕捉出来なかった場合、最後のexcept は『全ての例外』を意味します。分岐のelseと同じ意味を持ちます。
ここまで到達した場合は「何らかのエラーが発生」と出力します。

複数の例外を書かずにexcept : だけ用意すれば全ての例外を捕捉してくれますが、
全ての例外を同じexcept句で捕捉してしまうと、例外発生の原因が分かりにくくなるので、
各例外の種類を書いた上で最後にexcept :を記述すると良いでしょう。

## else/finally
elseは「try句内で例外が発生せずに最後まで処理が進んだ場合」処理を実行します。
finallyは「例外の発生に関係なく、最後に」処理を実行します。

```py3:try_else_finally.py
try:
    a = 10 / 0
#    a = 10 / 1
    print("{0}".format(a))
except ZeroDivisionError as e:
    print("ZeroDivisionError!!")
else:
    print("else statement")
finally:
    print("finally statement")
```

上記のプログラムを実行してください。その後、a = 10 / 0 をコメントアウトし、下の a = 10 / 1 のコメントを外して
再度実行し、両者の出力が説明のとおりであることを確認してください。

## raise
raiseを使用すると、故意に例外を発生させることが出来ます。
**raise** *引き起こしたい例外(送出したい例外)* を例外を発生させたい箇所に記述し、
except句の中でraise と記述することで、例外を返します。
以下のプログラムを実行後、except句内のraiseをコメントアウトして両者の動作の違いを確認してください。

```py3:raise.py
try:
    raise NameError('HiThere')
except NameError:
    print('An exception flew by!')
    raise
```

例外の説明は以上になります。

次: [Python基礎講座(12 関数)](http://qiita.com/Usek/items/09e30dab25be51d8023f)