Pythonで学ぶ 基礎からのプログラミング入門 (30) 例外処理について学ぼう(中編)

ニュース
トップ

【連載】

Pythonで学ぶ 基礎からのプログラミング入門

30 例外処理について学ぼう(中編)

伊藤裕一  [2015/12/14]
  • >
  • >>

30/30

前編では基本的な例外処理の手法、および例外の種類について扱いました。中編では、

  • 例外処理の場合分け
  • どのような例外が発生しているかの確認
  • 例外をわざと発生させるテクニック

について解説していきます。

例外処理の場合分け

前回は、try/exceptについて学びました。簡単に復習すると、例外が発生する可能性のある箇所を「try」で囲み、例外が発生した場合の処理を「except」に書くのでした。

たとえば以下のプログラムを実行するとします。

print('1: outside of try/catch')

try:
    print('2: inside of try scope')
    5 / 0
    print('3: inside of try scope')
except Exception:
    print('4: inside of except(catch) scope')

print('5: outside of try/catch')

このとき、tryの中にある5/0で例外が発生し、その例外がexceptでキャッチされるので、出力は以下のようになります。

# python test.py
1: outside of try/catch
2: inside of try scope
4: inside of except(catch) scope
5: outside of try/catch

5/0で例外が発生しており、tryの中での処理は打ち切られてしまうため、'3: inside of try scope'の出力はありませんね。また、この5/0をコメントアウトすると、出力は以下のように変わります。

python test.py
1: outside of try/catch
2: inside of try scope
3: inside of try scope
5: outside of try/catch

'3: inside of try scope'が表示されるようになった代わりに'4: inside of except(catch) scope'が表示されなくなっています。これは、tryの中で例外が発生しないとexceptが呼び出されないためです。

今回はこれに加えてelseとfinallyを扱います。それぞれtryやexceptと同じような構文を持っており、その役割を簡単に説明すると、

  • else: 例外が発生しなかった場合のみ処理される
  • finally: 例外が発生してもしなくても処理される

となります。

正直なところ、try/exceptに比べると使いどころはあまり多くないのですが、elseは例外が発生しなかった場合の処理に使い、finallyは必ず実施したい処理がある場合に使います。使いどころがなかなか難しく、私は「あえてこの処理をします」という表明の目的以外では両者を使いません。

たとえば、オープンしたファイルをfinallyでクローズするということは言語を問わずよく実施されますが、try/catchを抜けた箇所でのクローズでもだいたいカバーできます。ただ、finallyにあえてクローズ処理を書くことで、「このtry/catchでファイルの資源を開放することを保証します」ということが、ほかの人にも伝わるようになります。

少しコードを書いて実験してみましょうか。基本は前回と同じですが、今回はelseとfinallyが追加されています。

try:
    print('1: start of try')
    5 / 0
    print('2: end of try')

except Exception:
    print('3: error happens')

else:
    print("4: error doesn't happen")

finally:
    print('5: finally')

注目して欲しいのは、例外が発生したあとにどの処理が実行されているかということです。とりあえず実行してみます。

# python test.py
1: start of try
3: error happens
5: finally

例外が発生して、exceptの処理とfinallyの処理が実行されていることがわかります。 次に、例外を発生させなくした場合です。0による除算をコメントアウトします。

try:
    print('1: start of try')
    # 5 / 0
    print('2: end of try')

except Exception:
    print('3: error happens')

else:
    print("4: error doesn't happen")

finally:
    print('5: finally')

これを実行すると以下のようになります。

# python test.py
1: start of try
2: end of try
4: error doesn't happen
5: finally

今度はexceptが呼ばれなくなり、代わりにelseが呼ばれています。一方、finallyはまたもや呼ばれています。それほど複雑な動きではないと思うので、elseとfinallyの話はこのあたりで打ち切ります。なお、finallyでファイルをクローズする以下のコードは、

try:
    f = open('hello.txt', 'r')
    for line in f:
        print(line),

except:
    print('error')

finally:
    f.close()

finnalyを使わなくても、以下のように書くことができます。

try:
    f = open('hello.txt', 'r')
    for line in f:
        print(line),

except:
    print('error')

f.close()

これと同じようにelseも、わざわざ作らなくても「try の最後の場所に書く」ことでエラーが発生していないときだけ実行されますので、厳密に処理としてelseが必要という場合でなければ積極的に使うというものではないかもしれません。

例外処理をしつつ例外の内容を把握する

try/exceptで例外をキャッチできることはわかりました。ただ、やみくもに例外をキャッチするのは正直なところあまり行儀はよくないので、「正しくエラーをキャッチ」してあげる必要があります。たとえば、以下のコードがあるとしましょう。

try:
    5/0
    a = [1,2,3]
    print(a[3])

except Exception:
    print('error happens')

このコードではいつものように5/0でエラーが起きますが、それをコメントアウトしてもその後のリストの範囲外へのアクセスでエラーが発生します。このコードには問題があるためエラーの発生自体は仕方がないのですが、このコードで問題となるのは、

  • はたして0除算とリストの範囲外へのアクセスを同等に扱うべきか
  • そもそもこれは「例外処理」で対処するのではなくバグ修正すべきでは

といったことです。

今回のような短いコードであればすぐに問題は見つかるかもしれませんが、実際にはもっと長く複雑なコードが、ひとつのtryのカバー範囲になります。そのため「何が原因でどこでどのようなエラーが発生したのか」を正確に把握し、可能な限り例外処理ではなくエラーの根本原因を潰していくべきです。これをしっかり把握できれば、「何を修正すべき」か、「どのような例外クラスを使うべきか」がよくわかります。

実はこの原因と問題の把握は、try/catchを利用していなければできていました。たとえば上記コードのtry/catchを外して実行すると、5/0を実行した際に処理の中断とともに以下のようなエラー出力が得られてどこで何が発生していたのか一目でわかります。

python test.py
Traceback (most recent call last):
  File "test.py", line 1, in <module>
    5/0
ZeroDivisionError: integer division or modulo by zero

また、a[3]での範囲外のアクセスも、

python test.py
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    print(a[3])
IndexError: list index out of range

というように、リストの範囲外アクセスによるエラーを起こしていることがわかります。このエラー出力を「try/catch の処理中」に得られれば、処理の中断なしに問題原因の把握、および修正ができるようになります。これを実現するためにはstack traceと呼ばれている「エラーの発生までの流れ」をtry/catchの中で得る必要があります。それにはいくつかの手法があるので順番に紹介します。

traceback モジュール

今までのコードに手を加えずに実行できるのが、tracebackモジュールを使うという方法です。簡単なのでコードを見てみましょう。なお、exceptの後に何も例外クラスを書いていませんが、このようにするとExceptionを指定したのと同じ動きをします。

import traceback

print(1)

try:
    5/0
except:
    print(traceback.format_exc())

print(2)

インポートしたtracebackのformat_exc()関数を呼び出すことで、エラーの内容を表示しています。また、エラーもtry/catchで囲まれており、例外処理が働いているので、プログラムは中断されておらず、最後のprint(2)も実行されます。

1
Traceback (most recent call last):
  File "test.py", line 6, in <module>
    5/0
ZeroDivisionError: integer division or modulo by zero

2

このtracebackモジュールは多機能なので、より詳細な使い方については以下のドキュメントをご確認ください。

例外の処理を変数に格納する

前回お話ししたように、try/catchのexceptはエラーの内容に応じたものが呼び出されます。具体的にはExcept、IOErrorなどがありましたね。この例外処理の宣言時に変数を宣言することで、その例外を変数に格納することができます。これもそれほど難しくないのでコードで確認してみます。

import traceback

print(1)

try:
    5/0
except Exception, e:
    print(type(e))
    print(e)

print(2)

exceptの後に、Exceptionに続けてeがありますね。このeが例外を格納している変数です。eは実際にはException型ではなく「Exceptionおよびその子クラスのいずれか」が格納されていますので、typeとともにその内容を表示させています。さっそく、実行させて出力を見てみましょう。

1
<type 'exceptions.ZeroDivisionError'>
integer division or modulo by zero
2

ZeroDivisionErrorが発生していることがわかりますね。printさせれば、なぜZeroDivisionErrorになったかがわかります。

なお、このexceptは以下のように書くこともできます。

except Exception as e:
    print(type(e))
    print(e)

次回は例外処理の後編となります。

  • 意図的に例外を発生させる方法
  • 例外を呼び出し元でハンドルする
  • カスタム例外クラスの作成
  • assertを使ったテスト
  • withとas

を扱います。

執筆者紹介

伊藤裕一(ITO Yuichi)

シスコシステムズでの業務と大学での研究活動でコンピュータネットワークに6年関わる。専門はL2/L3 Switching とデータセンター関連技術およびSDN。TACとしてシスコ顧客のテクニカルサポート業務に従事。社内向けのソフトウェア関連のトレーニングおよびデータセンタとSDN関係の外部講演なども行う。

もともと仮想ネットワーク関連技術の研究開発に従事していたこともあり、ネットワークだけでなくプログラミングやLinux関連技術にも精通。Cisco社内外向けのトラブルシューティングツールの開発や、趣味で音声合成処理のアプリケーションやサービスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009年度 IPA 未踏プロジェクト採択

詳細(英語)はこちら

  • >
  • >>

30/30

インデックス

連載目次
第30回 例外処理について学ぼう(中編)
第29回 例外処理について学ぼう(前編)
第28回 【番外編コラム】Pythonプログラムの配布方法
第27回 第19回~第25回の演習の解説と解答例
第26回 オブジェクト指向について学ぼう(8)
第25回 オブジェクト指向について学ぼう(7)
第24回 オブジェクト指向について学ぼう(6)
第23回 オブジェクト指向について学ぼう(5) - ゲーム作りにチャレンジ
第22回 オブジェクト指向について学ぼう(4)
第21回 オブジェクト指向について学ぼう(3)
第20回 オブジェクト指向について学ぼう(2)
第19回 オブジェクト指向について学ぼう(1)
第18回 【番外編コラム】関数型プログラミングとPython
第17回 第11回~第16回の演習の解説と解答例
第16回 Pythonをシェルスクリプトのように使ってみよう(後編)
第15回 Pythonをシェルスクリプトのように使ってみよう(前編)
第14回 Pythonで日本語を扱うには? - 文字コードについて理解しよう
第13回 正規表現をマスターしよう
第12回 Pythonでテキスト処理/ファイル処理をしてみよう
第11回 知っていると便利な「型」について学ぼう
第10回 【番外編コラム】著者が開発した音声合成サービス/業務アプリはどう作った?
第9回 第8回までの演習の解説と解答例
第8回 ユーザーからプログラムへの入力をする方法
第7回 関数とモジュールを使いこなそう
第6回 プログラムの制御構造を理解しよう - 条件分岐とループ処理
第5回 「型」と「変数」について学ぼう(後編)
第4回 「型」と「変数」について学ぼう(前編)
第3回 まずは手を動かしてプログラミングの全体像を知ろう!
第2回 プログラミングの環境を整えよう
第1回 Pythonでプログラミングを学ぶ理由とは?

もっと見る

関連したタグ


転職ノウハウ

あなたの仕事適性診断
あなたの仕事適性診断

4つの診断で、自分の適性を見つめなおそう!

Heroes File ~挑戦者たち~
Heroes File ~挑戦者たち~

働くこと・挑戦し続けることへの思いを綴ったインタビュー

はじめての転職診断
はじめての転職診断

あなたにピッタリのアドバイスを読むことができます。

転職Q&A
転職Q&A

転職に必要な情報が収集できます

ドS美人面接官 vs モテたいエンジニア
ドS美人面接官 vs モテたいエンジニア

入室しようとしたら、マサカリ投げられちゃいました!?

特別企画 PR

人気記事

一覧

イチオシ記事

新着記事

TEDDY'S bigger burgers、冬をホットに乗り切る「ジンジャーバーガー」発売
[21:33 12/15] ライフ
三重県のサークルKサンクスで伊勢志摩サミット応援商品を発売
[21:33 12/15] ライフ
「Galaxy S7」は感圧ディスプレイと網膜センサー搭載で来年2月発表か
[21:33 12/15] 携帯
『チェンクロ』新章"年代記の大陸"配信開始、新キャラ声優に緑川光ら参加
[21:30 12/15] ホビー
『百日紅~Miss HOKUSAI~』アジア太平洋映画賞で最優秀アニメーション賞に
[21:20 12/15] ホビー

特別企画 PR

求人情報