質問文中で引用されているサイト(https://www.sejuku.net/blog/23044)の間違った記述のために、無用な混乱をさせられていることが理解の妨げになっていると思いますので、それを先に指摘しておきます。
「例外が発生した後の処理とは」と題して、
「except」ブロックで実行する処理は、様々なパターンがあるのでそれぞれご紹介していきたいと思います
と言っておきながら、
故意に例外を起こす場合は「raise」句を使用します。
以下のサンプルコードをご覧ください。
try:
raise ZeroDivisionError
except:
print('故意にエラーを発生')
と文章が続くのはおかしいと思いませんか?
この流れなら、except 節に raise 文のあるサンプルコードを示して、その説明をするべきでしょう。なぜ try 節に raise 文のあるコードが示されているのでしょうか(さらにいえば raise は文であって句ではありません)。
上記のコードは Python の文法的には有効なコードですが、try 文を使った処理パターンを説明するという目的からいえば、間違い(あるいは場違い)と言っていいと思います。
ついでに言えばそのサイトの冒頭で
Pythonではtry-exceptで「例外処理(exception)」を記述しておくことで、予期せぬエラーを未然に防ぐことができます
と書かれているのも間違いですね。
try 文で行われているのは、発生が予想されるエラー(例外)に対して、そのエラーが発生した際の処理を書いておくことです。予期できないエラーを未然に防ぐことはできません。当然ですね。さらに言えば「例外処理(exception)」と書かれていますが exception は例外であって例外処理ではありません。
さて、try 文を使った処理パターンとして、(try 節ではなく)except 節で raise 文が実行されるパターンは実際にあります。問題となっている
https://github.com/gabrieleilertsen/hdrcnn/blob/master/img_io.py
のコードも、そのひとつということになります。
except 節で raise 文が実行されるパターンで行われていることは、「try 節で例外を捕捉した後に、いったん何らかの処理を経た上で、例外を送出し直してプログラムを終了する」です。
except 節で行われる処理が、その「何らかの処理」ということになります。
最終的に送出される例外は、try 節で発生した例外をそのまま送出しなおすこともできますし、それとは別の例外を送出することもできます。別の例外を送出した場合は try 節で発生した例外も同時に送出されますが、これを隠すこともできます。
except 節でどのような処理を行うかは、プログラムによっていろいろでしょうが、問題になっているプログラムの場合を具体的に見てみましょう。
python
1class IOException(Exception):
2 def __init__(self, value):
3 self.value = value
4 def __str__(self):
5 return repr(self.value)
6
7...
8
9def writeLDR(img, file, exposure=0):
10 ...
11
12 try:
13 scipy.misc.toimage(sc*np.squeeze(img), cmin=0.0, cmax=1.0).save(file)
14 except Exception as e:
15 raise IOException("Failed writing LDR image: %s"%e)
https://github.com/gabrieleilertsen/hdrcnn/blob/master/img_io.py
このコードでは、最終的に送出されている例外は、ユーザ定義例外である IOException です。
例外の送出の前に行われている処理は特にありませんので、このコードの場合はユーザ定義例外の送出自体が「何らかの処理」ということになります。
ユーザ定義例外 IOException では、try 節で捕捉された例外のエラーメッセージを、自らのエラーメッセージの一部に取り込んでいます。
このコードで実際にどのようなエラーメセージが表示されるかを示すために、次のような内容のサンプルモジュール raise_test.py を用意しました。
python
1class IOException(Exception):
2 def __init__(self, value):
3 self.value = value
4 def __str__(self):
5 return repr(self.value)
6
7def writeLDR():
8 try:
9 1/0
10 except Exception as e:
11 raise IOException("Failed writing LDR image: %s"%e)
このモジュールを読み込んでwriteLDR関数を実行した結果は次のようになります。
python
1>>> import raise_test
2>>> raise_test.writeLDR()
3Traceback (most recent call last):
4 File "/Users/xxxx/raise_test.py", line 12, in writeLDR
5 1/0
6ZeroDivisionError: division by zero
7
8During handling of the above exception, another exception occurred:
9
10Traceback (most recent call last):
11 File "<stdin>", line 1, in <module>
12 File "/Users/xxxx/raise_test.py", line 14, in writeLDR
13 raise IOException("Failed writing LDR image: %s"%e)
14raise_test.IOException: 'Failed writing LDR image: division by zero'
このように、当初 try 節で発生した例外と、except 節で raise した例外の両方が、同時に送出されています。
念のためコードの動きをひとつひとつ見ていくと、
scipy.misc.toimage(sc*np.squeeze(img), cmin=0.0, cmax=1.0).save(file)
の実行で例外が発生すると、その例外は補足され、except節に実行が移ります。
except Exception
とありますので、捕捉される例外は、SystemExitやKeyboardInterruptなどを除いた、ほぼすべての例外です。なぜここで Exception を入れたのかというと、発生した例外をas e
で取得して、except 節での処理で使いたかったからだと思います。何らかの具体的な例外クラス名を指定しないと、as 構文は使えませんので。
except Exception as e
により、変数e
に try 節で発生した例外の例外オブジェクトが代入されます。
"Failed writing LDR image: %s"%e
により、e に代入されている例外オブジェクトの__str__
メソッドの返す値が、IOExceptionのエラーメッセージの一部(%s の位置)に組み込まれます。例外オブジェクトの__str__
メソッドの返す値とは、その例外のエラーメッセージです。
IOException("Failed writing LDR image: %s"%e)
により、ユーザ定義例外であるIOExceptionがインスタンス化されます。
raise IOException("Failed writing LDR image: %s"%e)
により IOException 例外が送出されます。try 節で捕捉された例外もこのとき同時に再送出されます。
なぜこのような例外処理の方法を選んだのかは、このプログラムを書いた当人に聞いてみないとわかりません。上のようにした方が多少なりともユーザフレンドリだと思ったからかもしれませんし、Pythonの例外処理の書き方を学習した時にそのようなパターンで覚えたからそれをそのまま踏襲しているだけかもしれません。実際のところはわかりません。
ちなみに、
raise IOException(...)
とせずに単に
raise
とすると、try 節で捕捉された最初の例外がそのまま再送出されます。「式を伴わなければ、 raise は現在のスコープで最終的に有効になっている例外を再送出します」とはそういう意味です。
python
1>>> try:
2... 1/0
3... except:
4... raise
5...
6Traceback (most recent call last):
7 File "<stdin>", line 2, in <module>
8ZeroDivisionError: division by zero