Python の例外処理

追記 2018/05/23: Python の例外に興味がある方には次の記事も参考になるかもしれません。よろしければご覧ください。

Python における例外処理について説明してみたいと思います。

Python では、例外処理「 try ... catch ... 」のロジックは try ```` except というキーワードを使って実現することができます。

以下、最も基本的な形から順に Python での例外処理の方法について見ていきましょう(ここでご紹介する記法は Python 2.x のものです。 Python 3.x の場合は少し異なるのでご注意ください)。

try - except

最も基本的な形は tryexcept を使ったパターンです。

try:
    f = open('sample.txt', 'r')
except:
    print 'cannot open sample.txt'

この場合、 try のブロックの中にある処理の中で例外(エラー)が発生したら except ブロックへと処理が移ります。

try ブロックの中で例外が発生しなかった場合は、 except のブロックは実行されず、その後の処理へと進んでいきます。

try - except ExceptionName

キーワード except に後にスペースを挟んで例外のクラスを指定すると、キャッチする例外を絞り込むことができます。

try:
    f = open('sample.txt', 'r')
except IOError:
    print 'cannot open sample.txt'

このコードの場合だと、入出力に関わる IOError という例外のみがキャッチされます。その他の例外があがった場合には except ブロックの処理は実行されません。

exept ブロックは複数個セットすることができます。例えば、次のようにすると、 PermissionError が あがったときとその他の例外があがったときとで表示するメッセージを切り替えることができます。

import sys

try:
    f = open('sample.text', 'r')
except PermissionError:
    print 'パーミッションエラーです。'
except Exception:
    print 'その他の例外です。'

except ブロックは上から順番に一致チェックがかかり、マッチした最初の except ブロックのコードのみが実行されることに注意してください。

つまり、上のコードの場合だと、 try ブロックで PermissionError があがった場合には「パーミッションエラーです。」というメッセージのみが表示され、「その他の例外です。」というメッセージは表示されません。処理の流れのイメージとしては、 if ... elif ... elif に近いイメージです。

上から順にチェックがかかるので、プログラマーは「捕捉範囲の狭い例外を先に、範囲の広い例外を後に記述すること」を心がける必要があります。具体的には、 Python では例外はクラスとして表現されているので、これは「子クラスを先に、親クラスを後に記述すること」を意味します。

ちなみに Python 2.x の場合、組み込みの例外には次のような親子関係があるので、複数の except ブロックを書く場合は、これを確認した上で記述するようにするとよいでしょう。

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
       +-- ImportWarning
       +-- UnicodeWarning
       +-- BytesWarning

Python 2.x 、 3.x それぞれの組み込みの例外について詳しくは公式ドキュメントを参照ください。

try - except ExceptionName, var

キャッチした例外の中身を確認したい場合は、 except の行を exept 例外クラス, 変数名 と書く形になります。

try:
    f = open('sample.txt', 'r')
except Exception, e:
    print e, 'error occurred'

必ずそうしないといけない決まりはありませんが、変数名には exception または省略形の e が選ばれていることが多いようです。

追記 20171222: Python 3.x の場合はここの記法が少し異なり、次のように記述する形になっています。

try:
    f = open('sample.txt', 'r')
except Exception as e:
    print(e, 'error occurred')

try - except - else

except ブロックの後には、オプションで else ブロックをつけることができます。

try:
    f = open('sample.txt', 'r')
except:
    print 'cannot open sample.txt'
else:
    lines = f.readlines()

else ブロックに記述された処理は、 try ブロックの中で例外が発生しなかった場合にのみ実行されます。

try - except - finally

else ブロックと同じく、オプションで finally ブロックをつけることができます。

finally ブロックの中の処理は、 try ブロック内での例外の発生の有無にかかわらず実行されます。

try:
    f = open('sample.txt', 'r')
except:
    print 'cannot open sample.txt'
finally:
    f.close()

elsefinally の両方を記述する場合は、 else を先に、 finally を後に記述するルールになっています。

try:
    # 例外が発生するかもしれない処理
except:
    # 例外が発生したときの処理
else:
    # 例外が発生しなかったときの処理
finally:
    # 例外の発生の有無にかかわらず必ず実行されるべき処理

finally ブロックは基本的には try から始まる一連の処理の最後に実行されますが、 try のブロックの中で例外が発生したのにそれががどの except でもキャッチされなかった場合には finally ブロックの処理が実行された後に try の中であがった例外が再度投げ直されるようになっています。

また、 try ブロックの中で break ```` continue ```` return のいずれかが実行されたときにはそこから脱出する間際に finally ブロックが実行されるようになっているようです。

このあたりの流れは少し複雑なので、 finally を正しく使いたい場合には 公式のドキュメント などをきちんと確認したうえで使うようにされるのがよいかと思います。

以上です。

例外処理に関しては、次のように PEP20 Zen of Python でも言及されています。

Errors should never pass silently. Unless explicitly silenced.

このあたり、 Python の言語設計のベースの考え方とも直結しているみたいです。

参考