注意してほしいのは、except節に書く例外クラスの順番だ。試しに、「except Exception as e:」節を一番上に書いてみよう(以下のコードではtry節を省略しているので、コードをコピー&ペーストで試している人は、上のコードでexcept節の順序だけを変えるようにしよう)。
while True:
try:
# 省略
except Exception as e:
print('Exception')
print(e.args)
print()
except ValueError as e:
print('Value Error')
print(e.args)
print()
except IndexError:
print('Index Error')
print()
print('try文の直後の行を実行しました')
print('無限ループを終了しました')
これでコードを実行して、先ほどと同様に選択肢「2」を入力するとどうなるだろう。
すると、上の画像のように「Exception」と表示された後に、例外の内容が表示された。「except Exception as e:」節で例外が捕捉されたということだ。実は例外クラスの多くはExceptionクラスを継承している(例外クラスの階層構造については次回に詳しく紹介する)。
この場合なら、「IndexError is Exception」の関係が成り立っている。つまり、「IndexErrorはExceptionでもある」ので「except Exception as e:」節でそれが捕捉されてしまったのだ。継承階層で上位にある例外クラス(「例外」という概念を幅広く取り扱うためのクラス)は、より具体的なクラスよりも後でexcept節に列挙する必要があるということだ。なお、例外クラスを指定しない「except:」節は全てのexcept節の最後に置く必要がある。これはあらゆる例外を捕捉するための節だ。
複数の(「is-a」の関係にない)例外を1つのexcept節で捕捉することも可能だ。この場合は、「except 例外クラス as 変数名」の「例外クラス」部分にそれらのクラスをタプルにまとめて並べればよい。このときタプルを囲むかっこ「()」は省略できないので注意すること。
以下に例を示す(先ほどと同様にtry節のコードは省略する)。
while True:
try:
# 省略
except (ValueError, IndexError) as e:
print(e.args)
print()
except Exception as e:
print('Exception')
print(e.args)
print()
except:
print('Exceptionよりも上位の例外クラスに属する例外が発生しました ')
print('try文の直後の行を実行しました')
print('無限ループを終了しました')
このコードでは、ValueError例外とIndexError例外を1つのexcept節で捕捉している。また、最後には例外クラスを指定しないexcept節を追加した。Exceptionクラスを捕捉するexcept節があるので、最後の「except:」節は例外クラスの継承階層の中でそれよりも上位にある例外(ユーザーが[Ctrl]+[C]キーを押して実行を中断したときに発生する例外など)を捕捉する(興味のある方は、上記コードを実行してユーザー入力を待っているタイミングで[Run]ボタンの右隣にある[interrupt the kernel]ボタンをクリックしてみよう)。
実行結果については省略する。
先ほど示した構文内のコメント内では、else節について「例外が発生しなかった場合に実行するコード」と書いた。また、finally節については「例外が発生してもしなくても最後に実行するコード」と書いた。これらについても少し見ておこう。
while True:
try:
print()
print('1: 例外を発生させる')
print('2: 例外を発生させない')
print('3: 終了')
selection = int(input('どれにしますか: '))
if selection == 1:
tmp = 'str'[5]
elif selection == 2:
print('例外を発生しませんでした')
elif selection == 3:
break
except IndexError:
print('IndexError例外が発生しました')
else:
print('else節を実行しました')
finally:
print('finally節を実行しました')
これまでの例と同様に、while文で無限ループを実行し、その中で「例外を発生させる」「例外を発生させない」「終了する」の3つから実行するものを選択するようになっている。このコードでelse節とfinally節の動作を確認してみよう。
選択肢「1」を選択すると、IndexError例外が発生する。このときには次のような画面になる。
「IndexError例外が発生しました」に続けて「finally節を実行しました」と表示されていることに注目しよう。この出力からは、例外処理が終わると、finally節が実行されることが分かる。
次に選択肢「2」を選択してみる。
今度はif文の該当する節で「例外を発生しませんでした」が表示された後に、例外が発生していないのでelse節に実行が移り「else節を実行しました」が表示され、最後にfinally節に実行が移って「finally節を実行しました」と表示されている。これらからfinally節が、例外の発生の有無に関係なく実行されることが分かったはずだ。ちなみに選択肢「3」を選択しても、finally節は実行される(try文が終了する前には必ずfinally節に記述した内容が実行されるということだ)。
else節はそれほど使われることがないと思われる。ただ、ファイルを開くような処理を行う際には、ファイルを開いて、その内容を読み書きして、最後にファイルを閉じるという流れになるだろう。そのときには、else節でファイルの内容を処理して、finally節で最後にファイルを閉じる処理を実行するといった使い方が考えられる。これらについてはファイルを扱う回で紹介する予定だ。
先ほどのコードではIndexError例外だけを処理していたことに気が付いただろうか。最初の例で述べたように、上のコードではinput関数の戻り値をint関数で整数値に変換しているのでValueError例外が発生する可能性がある。とすると、上のコードを実行して数字以外を入力したらどうなるだろう。実際に試してみよう。
例外が発生したときに、その例外を捕捉するexcept節がなければ、その例外は処理されず、実行はそのままfinally節へと移る。そして、finally節の後でその例外が再度発生する。今のコードだと、例外が発生したことが画面に表示されて終わりだが、関数呼び出しやメソッド呼び出しが絡んでいる場合には、例外はそれを呼び出した側に「伝播」する。簡単な例を以下に示す。
def raise_exception():
try:
raise ValueError()
except IndexError:
print('Index Error caught')
finally:
print('executing finally clause')
def call_raise_exception():
try:
raise_exception()
except ValueError:
print('Value Error caught')
def call_raise_exception2():
raise_exception()
call_raise_exception()
call_raise_exception2()
raise_exception関数ではraise文を使って、ValueError例外を発生させている(raise文については次回詳しく説明する)。try文で例外処理を行っているが、処理しているのはIndexError例外だけなのでここでは処理されず、finally節が終わった後にValueError例外はそれを呼び出した側へと伝播する。
call_raise_exception関数とcall_raise_exception2関数では、raise_exception関数を呼び出しているが、前者では例外処理を行って、後者では行っていない。最後にモジュールのトップレベルでこれらの関数を呼び出している。
これを実行すると次のようになる。
call_raise_exception関数を呼び出すと、その内部でraise_exception関数が呼び出される。そこで発生したValueError例外はraise_exception関数内では処理できなかったので、そのfainally節で「executing finally clause」と表示された後に、call_raise_exception関数に伝播され、そのtry文(のexcept節)で捕捉される。そのため、ここでは「Value Error caught」と表示される。
その次のcall_raise_exception2関数では例外処理を全くしていないので、「モジュールのトップレベル→call_raise_exception2関数→raise_exception関数」という関数の呼び出し階層をさかのぼって例外が伝播して、最終的にこれまでによく見てきたエラー(例外)発生画面が表示されているわけだ。
例外は、それが発生した場所から、関数やメソッドの呼び出し階層とは逆の方向へと順次伝わっていくので、それらの階層のどこかで処理を行う必要があることは覚えておこう。
今回は例外と例外処理の基礎知識を紹介した。次回は、例外クラスの階層構造を見た後で、自分で例外を生成する方法と、自分で例外クラスを定義する方法について見ていこう。
Copyright© Digital Advantage Corp. All Rights Reserved.