例外処理

例外の捕捉

たとえば、存在しないファイルを読み込もうとするとエラー(例外)が発生する。

import sys

file = sys.argv[1]

content = open(file, "r").read()
size = len(content)
print file + ": " + str(size)
^o^ > python exception1.py sample.dat
Traceback (most recent call last):
  File "exception1.py", line 5, in 
    content = open(file, "r").read()
IOError: [Errno 2] No such file or directory: 'sample.dat'

上の例では sample.dat を読み込もうとしているが、存在しないファイルなので IOError が発生してプログラムが終了している。
ここでプログラムが終了しないようにするには、try ~ except 文を使って例外を適切に処理してやればいい。

import sys

file = sys.argv[1]

try:
    content = open(file, "r").read()
    size = len(content)
    print file + ": " + str(size)
except IOError:
    print "Cannot open file: " + file

例外が発生するかもしれない処理をtry節に書き、発生した例外はexcept節で捕捉する。こうすると例外が発生したときの処理を書くことができる。この例では単純にメッセージを出力して終わっているだけだけど。
実行結果:

^o^ > python exception2.py sample.dat
Cannot open file: sample.dat

except節の処理が実行されているのがわかる。

例外の発生

何らかの理由で例外を発生させたい場合には、raise 文を使う。一番単純な使い方としては 「raise メッセージ」とすればいい。

try:
    raise "Someting error."
except:
    print "Error occured."
^o^ > python exception3.py
Error occured.

この場合、発生する例外は StandardError みたい。特定の種類の例外を発生するには「raise 例外の種類(メッセージ)」という形式にする。

クラス (3)新スタイルクラス

__slots__

Python のクラスには旧スタイルクラスと新スタイルクラスがあるらしい。ややこしいな。
新スタイルクラスを作るには、ビルトイン型を継承するか、もしくは最上位のクラスであるobjectを継承する。

class Person(object):

    __slots__ = ["name", "age"]

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greeting(self):
        print "Hello, I'm " + self.name + "!"

新スタイルクラスの機能の一つとして、__slots__によってアクセスできるアトリビュートを制限できるというのがある。上の例では、アクセス可能なアトリビュートを name と age に制限している。これで勝手なアトリビュートを追加したりはできなくなったはずだ。やってみよう。

>>> from person2 import Person
>>> andy = Person("Andy", 32)
>>> andy.name
'Andy'
>>> andy.age
32
>>> andy.job = "programmer"
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'Person' object has no attribute 'job'

__slots__ で指定している name と age にはアクセスできるけど、job に代入しようとしたら ‘job’なんてアトリビュート無いよ、と怒られた。

プロパティ

ただし、まだ name と age には値を代入できてしまう。

>>> andy.name = "Bill"
>>> andy.name
'Bill'

名前は変わらないのだから、参照はできても代入はできてほしくない。こういうときはプロパティにすればいいらしい。

class Person(object):

    __slots__ = ["_name", "age"]

    def __init__(self, name, age):
        self._name = name
        self.age = age

    def greeting(self):
        print "Hello, I'm " + self._name + "!"

    def get_name(self):
        return self._name

name = property(get_name, None)

名前用のアトリビュートを _name に変更して、参照用の get_name メソッドを作った。そしてproperty関数を使ってnameというアクセサを作っている。property関数の引数にはゲッター、セッターのメソッド指定する。セッターとしては None を指定しているから、これで name は参照だけできるようになったはずだ。

>>> from person2a import Person
>>> andy = Person("Andy", 32)
>>> andy.name
'Andy'
>>> andy.name = "Bill"
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: can't set attribute

name に代入しようとしたら、アトリビュートをセットできない、といわれた。うまくいってるようだ。

でもまてよ。_name アトリビュートにはアクセスできるはずだから、代入もできてしまうんじゃないか?

>>> andy._name = "Bill"
>>> andy.name
'Bill'

できちゃうじゃん。だめじゃん。
じゃあ、__slots__ から _name を取り去ったらどうだ。

class Person(object):

    __slots__ = ["age"]

    def __init__(self, name, age):
        self._name = name
        self.age = age

    def greeting(self):
        print "Hello, I'm " + self._name + "!"

    def get_name(self):
        return self._name

name = property(get_name, None)
>>> from person2b import Person
>>> andy = Person("Andy", 32)
Traceback (most recent call last):
  File "", line 1, in 
  File "person2b.py", line 6, in __init__
    self._name = name
AttributeError: 'Person' object has no attribute '_name'

だめだ。今度は _name アトリビュートがないって怒られた。どうすりゃいいんだ。