試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Pythonの例外とその処理に関する覚え書き(概要、書式、後始末処理)

以前Pythonの基本的なことをまとめたとき、例外については参考URLのリンクを貼り付けただけだったので、この機会にまとめてみた。
(2014/11/12)リンク先を修正し、サンプルコードの内容もPython 3対応のものを追加し、本文の記述も一部書き直している。

  1. 例外について
  2. 種類
  3. 基本的な形
    1. 特定の例外に対して処理を行う場合
    2. 任意の例外に対して処理を行う場合
  4. 後始末の処理を含めた例外処理

例外について

何かの操作を行うとき、何らかの原因によってそれが「うまくいかない」可能性を持っている場合がある。言語/環境によっては、そのときに行っていた処理を途中でやめて、失敗したことを環境側に知らせる仕組みがある。このとき、発生した「ある種の異常」(と定義されているもの)を例外といい、例外が起こったときにはそのままではプログラムは発生した例外の情報を出力しつつ終了してしまうが、例外が起こる可能性がある処理の範囲をマークした上で例外発生時の処理を別途記述することにより、プログラムを終了させることなく、例外発生時にそれを捉えて適切な処理をさせることが可能となっている。
Pythonにも例外を扱う機能がある。

種類

標準ではライブラリリファレンスに書かれているだけの種類がある。状況によってどれが出るのかはリファレンスに載っている場合もあるが、わざと失敗させて調べる方法もある。IOErrorやOSErrorのようなものは処理に関連していることが多く分かりやすい。

基本的な形

上の「例外が起こる可能性がある処理の範囲をマーク」という部分に対応するのがtry節、「例外発生時の処理を別途記述」に対応するのがexcept節(例外ハンドラ)となる。except節では「except [例外名] as [オブジェクト名]」として記述することで得られる例外オブジェクトを用いて例外に関する情報を

  • 例外オブジェクト自身
  • 例外の発生した状況に特有のメンバ(errnoやstrerrorなど)

のような形で取得することができる。
Python 2では「except IOError, (errno, msg):」のような書き方もあった。

特定の例外に対して処理を行う場合

try:
  # [失敗して例外名1もしくは例外名2の例外を発生させる可能性のある処理(関数呼び出し)...]
except [例外名1] as [例外オブジェクト1]:
  # [例外名1の例外が発生した場合の処理...]
except [例外名2] as [例外オブジェクト2]:
  # [例外名2の例外が発生した場合の処理...]
# ...

下はファイルを「開く」操作における例外処理の例。

try:
  f = open ('[入力ファイルの場所]', 'r')
except IOError as e:
  # [開くのに失敗した場合の処理...]
  # エラー番号は「e.errno」,メッセージは「e.strerror」として取り出す

この例ではIOErrorという例外が発生したときにその情報を含んだオブジェクトを「e」として(エラー番号は「e.errno」、エラーメッセージは「e.strerror」となる)その中の処理(この場合は情報の表示のみ)を実行する。これらの値を参照(使用)しない場合はこの「as [例外オブジェクト]」の部分は記述する必要はない。
ファイルを開くのに失敗した場合など、OS関係の各種エラーメッセージは

import locale
locale.setlocale (locale.LC_ALL, '')

などのようにしてロケールを設定することで国際化されたものが表示できる。

任意の例外に対して処理を行う場合
もし例外名を指定せず

except:
  # [処理...]

とした場合には任意の例外に対して共通した例外処理を行う。ただし、1つのtry節に対して複数のexcept節を記述している中でこれを記述する場合、探索の順番の関係で最後に書く必要がある。また、場合によっては予期されない例外がここで意図されず捉えられて、あるはずの問題を見えなくしてしまうという恐れもある。一方で、場合によっては余分な記述を省略できるという利点もある(import文など)。

後始末の処理を含めた例外処理

例外が発生したかどうかに関わらずに確実にある種の解放処理*1を行いたいことがある。このときにはfinally節にそれらの処理を記述するのだが、バージョン2.4系までのPythonでは「try-finally」のtry節の中に「try-except」を入れ子にする形を用いる必要がある。バージョン2.5以上では「try-except-finally」の書き方ができる。
下は実行時の作業ディレクトリにfile.txtがあるときに読み込みモードで開いて書き込みを試みている(意図的に例外を発生させている)。
(バージョン3系と2.6系以上のPythonで実行できるようにする場合)

#! /usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function  # 2.6系以上

import locale
import sys

locale.setlocale (locale.LC_ALL, '')

infile = 'file.txt'
try:    # ファイルが開けるかどうか
  f = open (infile, 'r')
  try:  # 確実に後始末を行うfinally節のためのtry節
    print ('try: f.write()')
    f.write ('test')
  except IOError as e:
    print ('except: Cannot write to {0}'.format (infile), file=sys.stderr)
    print ('  e: [{0}]'.format (e), file=sys.stderr)
  finally:
    print ('finally: close {0}'.format (infile))
    f.close ()
except IOError as e:
  print ('except: Cannot open "{0}"'.format (infile), file=sys.stderr)
  print ('  errno: [{0}] msg: [{1}]'.format (e.errno, e.strerror), file=sys.stderr)

(バージョン2.4系までのPythonで実行できるようにする場合)

#! /usr/bin/python
# -*- coding: utf-8 -*-

import locale
import sys

locale.setlocale (locale.LC_ALL, '')

infile = 'file.txt'
try:  # ファイルが開けるかどうか
  f = open (infile, 'r')
  try:  # 確実に後始末を行うfinally節のためのtry節
    try:  # 書き込みができるかどうか
      print 'try: f.write()'
      f.write ('test')
    except IOError, e:
      print >> sys.stderr, 'except: Cannot write to %s' % infile
      print >> sys.stderr, '  e: [%s]' % (e)
  finally:
    print 'finally: close %s' % infile
    f.close ()
except IOError, (errno, msg):
  print >> sys.stderr, 'except: Cannot open "%s"' % infile
  print >> sys.stderr, '  errno: [%d] msg: [%s]' % (errno, msg)

(2009/4/11)上のコードは「Pythonの例外とその処理に関する覚え書き(ユーザ定義の例外など)」で触れたelse節を利用して下のようにも書ける。その下に書いたバージョン2.5系以上向けのコードも同様に修正できる。
(バージョン2.4系までのPythonで実行できるようにする場合・その2)

#! /usr/bin/python
# -*- coding: utf-8 -*-

import locale
import sys

locale.setlocale (locale.LC_ALL, '')

infile = 'file.txt'
try:      # ファイルが開けるかどうか
  f = open (infile, 'r')
except IOError, (errno, msg):
  print >> sys.stderr, 'except: Cannot open "%s"' % infile
  print >> sys.stderr, '  errno: [%d] msg: [%s]' % (errno, msg)
else:     # 開くのに成功
  try:    # 確実に後始末を行うfinally節のためのtry節
    try:  # 書き込みができるかどうか
      print 'try: f.write()'
      f.write ('test')
    except IOError, e:
      print >> sys.stderr, 'except: Cannot write to %s' % infile
      print >> sys.stderr, '  e: [%s]' % (e)
  finally:
    print 'finally: close %s' % infile
    f.close ()

いずれのコードも、ファイルが読み込めれば

(Python 3系)
try: f.write()
except: Cannot write to file.txt
  e: [not writable]
finally: close file.txt

(Python 2.7系)
try: f.write()
except: Cannot write to file.txt
  e: [File not open for writing]
finally: close file.txt

読み込めなければ

except: Cannot open "file.txt"
  errno: [2] msg: [そのようなファイルやディレクトリはありません]

と表示される。
あるいは、同様にバージョン2.5系から使用可能なwith文により下のようになる。
(バージョン3系と2.6系以上のPythonで実行できるようにする場合)

#! /usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function  # 2.6系以上

import locale
import sys

locale.setlocale (locale.LC_ALL, '')

infile = 'file.txt'
try:    # ファイルが開けるかどうか
  with open (infile, 'r') as f:
    try:  # 確実に後始末を行うfinally節のためのtry節
      print ('try: f.write()')
      f.write ('test')
    except IOError as e:
      print ('except: Cannot write to {0}'.format (infile), file=sys.stderr)
      print ('  e: [{0}]'.format (e), file=sys.stderr)
  # ここでfのファイルは例外が発生した場合もしなかった場合も閉じられる
except IOError as e:
  print ('except: Cannot open "{0}"'.format (infile), file=sys.stderr)
  print ('  errno: [{0}] msg: [{1}]'.format (e.errno, e.strerror), file=sys.stderr)

こちらはファイルがあるときに「finally: close file.txt」の表示はないが、ファイルオブジェクトfを閉じる処理(close())はその後例外が発生するかどうかに関わらず自動的に行われる。

参考URL:

*1:主にファイルオブジェクトの類