Errors and Exceptions

ここではエラーと例外について勉強します。

Syntax Errors

Syntax Errorはよく見るエラーですね。

>>> strings = ['1', '2', '3']
>>> for str in strings
  File "<stdin>", line 1    # 「:」が抜けている
    for str in strings
                     ^
SyntaxError: invalid syntax


ちなみにSyntaxErrorではエラー検出箇所を「^」で教えてくれます。

Exceptions

例外!例外って知識としてはありますけど、使いこなすのってとっても難しいですよね。例外を使いこなしたコードはいかに美しいことか!C言語で育ったおじさんにはなかなか難しいのです。どうしても戻り値で返しちゃう癖が抜けない。


閑話休題。例外に関して分かりやすい定義があったので掲載しておきます。

Errors detected during execution are called exceptions

http://docs.python.org/tutorial/errors.html#exceptions
>>> 10 * (1 / 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> 4 + spam * 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects


名前はXxxxxErrorですが、例外なんですね。それぞれの例外では例外の原因が表示されています。自分で例外を定義する時も同じシンタックスで説明を表示するのがよいですね。

Handling Exceptions

それでは例外をキャッチしてみましょう。

>>> while True:
...     try:
...             x = int(raw_input("Please enter a number: "))
...             print '=> ', x
...     except ValueError:
...             print "Oops! That was no valid number. Try again..."
... 
Please enter a number: 1
=>  1
Please enter a number: 2
=>  2
Please enter a number: 3
=>  3
Please enter a number: a    # aは数値じゃないのでValueError発生
Oops! That was no valid number. Try again..
Please enter a number: ^C   # Ctrl-CはKeyboardInterruptを発生させるがキャッチしていないためwhileループから抜ける
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
KeyboardInterrupt


またその他全ての例外をキャッチすることも出来ます。

>>> import sys
>>> try:
...     f = open('myfile.txt')
...     s = f.readline()
...     i = int(s.strip())
... except IOError:
...     print "I/O error"
... except ValueError:
...     print "Could not convert data to an integer."
... except:   # ←ここ
...     print "Unexcepted error:", sys.exc_info()[0]
...     raise


またJavaのfinally節とちょっと違いますが、try節で例外が発生しなかった時に実行させる節を定義することが出来ます。

import sys

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'cannot open', arg
    else:
        print arg, 'has', len(f.readlines()), 'lines'
        f.close()


このスクリプト (exception_example.py) を実行すると

% python2.5 exception_example.py 
% python2.5 exception_example.py exception_example.py 
exception_example.py has 10 lines
% python2.5 exception_example.py ddd                 
cannot open ddd


確かに例外が発生したらelse:節は実行されませんね。

Raising Exceptions

これまでも出てきましたが、自分で例外を発生させるにはraiseという構文を使います。

>>> try:
...     raise NameError('HiThere')    # 例外の送信
... except NameError:
...     print 'An exception flew by!'
...     raise                         # 例外の再送
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: HiThere


またいわゆる「例外の再送」は引数なしでraiseとします。

User-defined Exceptions

では自分で例外を定義してみます。例外はExceptionクラスを継承して定義します (もちろん直接継承じゃなくてもOK)。

>>> class MyError(Exception):
...     def __init__(self, value):        # コンストラクタ
...             self.value = value
...     def __str__(self):                # 文字列化メソッド
...             return repr(self.value)
...
>>> try:
...     raise MyError(2 * 2)
... except MyError:
...     print 'My exception occurred'
...
My exception occurred


う〜ん、Python 2.5ではexcept構文のasが使えないんですよね。例外の引数にアクセスするにはどうしたらいいんだろうか?ということで調べてみたらこうでした。

>>> try:
...     raise MyError(2 * 2)
... except MyError, e:    # ← ここ!
...     print 'My exception occurred, value:', e.value
...
My exception occurred, value: 4


例外を作成するにあたってのベストプラクティスはこんな感じ。

  • モジュール内でベースとなる例外クラスを作成
  • 個々の例外は↑のベース例外クラスを継承して作成


また、例外の名前は「Error」という文字列が最後に付けるのがよいようです。「Exception」の方が分かりやすいような気が…。

Defining Clean-up Actions

やっぱりありました、finally。意味もJavaと全く同じですね。処理の最後に必ず実行されます。

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Goodby, world!'
...
Goodby, world!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
KeyboardInterrupt


もちろんexcept:ブロックの中で例外が投げられても問題ありません。

>>> try:
...     raise KeyboardInterrupt
... except KeyboardInterrupt:
...     print 'KeyboardInterrupt occurred'
...     raise
... finally:
...     print 'Goodby, world!!'
...
KeyboardInterrupt occurred
Goodby, world!!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
KeyboardInterrupt

Predefined Clean-up Actions

こちらはC#でいうところのusing節みたいなものですね。ちなみにC#ではこんな感じに書きます。

using (StreamReader reader = File.OpenText(@"c:\sample.txt")) {
  Console.WriteLine( reader.ReadLine());
}


Pythonではこのように書きます…と思ったら、with文を2.6からの機能でした。

>>> with open("workfile.txt") as f:
<stdin>:1: Warning: 'with' will become a reserved keyword in Python 2.6
  File "<stdin>", line 1
    with open("workfile.txt") as f:
            ^
SyntaxError: invalid syntax