[Python入門]例外と例外処理の基礎Python入門(2/2 ページ)

» 2019年09月06日 05時00分 公開
[かわさきしんじDeep Insider編集部]
前のページへ 1|2       

except節に書く例外クラスの順番

 注意してほしいのは、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('無限ループを終了しました')

Exception例外を捕捉するexcept節を先に書いたコード

 これでコードを実行して、先ほどと同様に選択肢「2」を入力するとどうなるだろう。

実行結果 実行結果

 すると、上の画像のように「Exception」と表示された後に、例外の内容が表示された。「except Exception as e:」節で例外が捕捉されたということだ。実は例外クラスの多くはExceptionクラスを継承している(例外クラスの階層構造については次回に詳しく紹介する)。

例外クラスの継承階層 例外クラスの継承階層
実際にはIndexErrorクラスはExceptionクラスを直接継承するのではなく、間にLookupErrorクラスを挟んでいる。そのため、上の図では点線でつないでいる。

 この場合なら、「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('無限ループを終了しました')

タプルを使って複数の例外を1つのexcept節で処理する

 このコードでは、ValueError例外とIndexError例外を1つのexcept節で捕捉している。また、最後には例外クラスを指定しないexcept節を追加した。Exceptionクラスを捕捉するexcept節があるので、最後の「except:」節は例外クラスの継承階層の中でそれよりも上位にある例外(ユーザーが[Ctrl]+[C]キーを押して実行を中断したときに発生する例外など)を捕捉する(興味のある方は、上記コードを実行してユーザー入力を待っているタイミングで[Run]ボタンの右隣にある[interrupt the kernel]ボタンをクリックしてみよう)。

 実行結果については省略する。

else節とfinally節

 先ほど示した構文内のコメント内では、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節を実行しました')

else節とfinally節の動作を確認するコード

 これまでの例と同様に、while文で無限ループを実行し、その中で「例外を発生させる」「例外を発生させない」「終了する」の3つから実行するものを選択するようになっている。このコードでelse節とfinally節の動作を確認してみよう。

 選択肢「1」を選択すると、IndexError例外が発生する。このときには次のような画面になる。

例外発生時には、その処理が終わった後にfinally節が実行される 例外発生時には、その処理が終わった後にfinally節が実行される

 「IndexError例外が発生しました」に続けて「finally節を実行しました」と表示されていることに注目しよう。この出力からは、例外処理が終わると、finally節が実行されることが分かる。

 次に選択肢「2」を選択してみる。

例外が発生しなかったときには、else節とfinally節が実行される 例外が発生しなかったときには、else節とfinally節が実行される

 今度は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関数」という関数の呼び出し階層をさかのぼって例外が伝播して、最終的にこれまでによく見てきたエラー(例外)発生画面が表示されているわけだ。

 例外は、それが発生した場所から、関数やメソッドの呼び出し階層とは逆の方向へと順次伝わっていくので、それらの階層のどこかで処理を行う必要があることは覚えておこう。

まとめ

 今回は例外と例外処理の基礎知識を紹介した。次回は、例外クラスの階層構造を見た後で、自分で例外を生成する方法と、自分で例外クラスを定義する方法について見ていこう。

今回のまとめ:

  • 例外とは、プログラム実行時にその実行を妨げるような状況や問題のこと
  • 例外が発生すると、プログラムの実行はそこで中断される
  • 例外の発生に備えて、それを処理するためのコードをあらかじめ記述できる(例外処理)
  • Pythonでは例外処理はtry文を使って記述する
  • try文のexcept節では、処理対象の例外を指定したり、発生した例外に関する情報を受け取ったりできる。実際に例外処理を行う際には、それらの情報に応じて処理を切り分ける
  • except節に例外クラスを指定する順序には注意が必要
  • 例外が発生しなかった時に実行したい処理はelse節に書ける
  • 例外が発生したか発生しなかったかに関係なく、最後に行うクリーンナップ処理はfinally節に書く
  • 処理されなかった例外は、関数やメソッドの呼び出し階層を遡って伝えられる
  • 最終的に例外が処理されないと、そこでプログラムの実行が中断することになる

前のページへ 1|2       

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

4AI by @IT - AIを作り、動かし、守り、生かす
Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。