たけっぱ横丁

the technical document for Vim(Editor), Natural Language Proecssing(NLP) tools and Programming(Python, Ruby, C++ etc).

言語処理のための例外処理

経緯

私,自然言語処理に携わる研究を行っているのですが 必然と大規模なテキストデータを扱います.
(恐らく大半の言語処理のプログラムを書いている方に訪れるであろう)鬼門が

どれだけ網羅しようともテキストデータに例外が存在する

ということです.

自然言語処理の大半は,この例外と戦うのではないのかというぐらいに 例外が発生しまくります.

これを取り除くために前処理(の前処理)という形で
テキストのフィルタリング・整形を行う必要が出てくるわけです.

それでも潰しても,潰しても消えない例外は存在するわけでして…
ならば例外が起こることを考慮したプログラムを書くのが一番です.

ということで今回の記事を書くに当たりました.

コンテンツ

  1. 例外処理の書き方
  2. 例外を定義する
  3. デコレータを使った賢い例外処理

1.例外処理の基本

pythonにおける基本的な例外処理は以下のように書きます.

try:
    # 例外が発生すると思われる処理
    pass
except AttributeError:
    # AttributeError という例外が発生した時の処理
    pass
except ValueError as e:
    # ValueError という例外が発生した時の処理
    # e に 例外の内容のインスタンスが格納される
    print e.args    #例外生成時に使用される変数
    print e.message #例外のメッセージ
except (ZeroDivisionError, RuntimeError, TypeError):
    # 複数の例外に対して一括の処理を書きたい場合は
    # この書き方
except :
    #上でキャッチしきれなかった例外. 取り扱い注意
    #エラーメッセージを送出した上で例外を更に投げるのに向いている.
    raise
else : 
    #例外が発生しなかった時にする処理
finally:
    #例外が起ころうが,例外が起ころまいが行う処理
    pass

それぞれのブロック(except, else, finally)は必要に応じで省略可能です. 例外が発生した場合, 行っていた処理は中断されます.

exceptには様々な種類の例外があります. (自作できます. なので一番はライブラリの例外を確認することです)

2. 例外を定義する

自分のプログラムに合った例外を作りましょう. 作り方は簡単です. Exceptionクラスを継承したクラスを作成するだけです.

class MyError(Exception): pass #例外の定義はこれだけ

def func(x):
    if x < 5: 
        raise MyError(x)
    return x

def main():
    func(5) # -> 例外は発生しない
    func(2) # -> MyErrorが発生する

自作の例外は, 一つ継承用の例外を作り,
それを継承すると他の例外を区別しやすく保守も容易になります.

class MyError(Exception): pass      #継承用の例外を用意する

class SpecifiedError(MyError): pass #上記を継承する.
class UnknownError(MyError): pass   

def func(x):
    if x < 5: 
        raise UnknownError(x)
    return x

def main():
    try:
        func(5)
    except MyError as e:
        #MyErrorを継承しているので,これでUnknownErrorの例外も処理できる
        print e

3.デコレータを使った賢い例外処理

さて,実はここまではただの前置きです. 例外処理では例外が発生すると, 処理を中断してまいます.

これって意外と困った処理だったりします. 例えば以下のプログラム

   def func(y):
       return 1./y

   def main():
       try:
           results = map(func, range(1000))
       except ZeroDivisionError:
           print '0除算は禁止です'

こんなプログラムを書くと,rangeの範囲は0~999ですので 当たり前ですが, ZeroDivisionErrorが発生します.

例外を捉えれるのはいいんですが, このプログラムだと, たった一つの例外(0)のために, 他の999個の処理の結果が失われるのです.

これってむちゃくちゃ非効率ですよね.

この非効率さを解決するには, もとのfunc関数をいじってやれば良いのでしょうが

これが公開されているライブラリとかになれば,かなり鬱陶しいことになります. そこで私がおすすめするのが以下の方法

def wrapper(func, x, *args, **keyword):
    try:
        return func(x)
    except ZeroDivisionError:
        return -1

@wrapper
def func(y):
    return 1./y

def main():
    results = map(func, range(1000))  # resultsには1000個のデータが入ることが保証される
    print results

これだけで,かなり鬱陶しい操作がなくなるのでお勧めですよ!