「1日1python」の時間です(ただし隔週放送)。

ようやく raise を理解したのでまとめるぜ。raise文との出会いはオライリーだった。raiseとは例外を飛ばすものとの事だったけど、そもそも例外って何かわかんねーしassertっていう何か似てるのもあるしで全く理解できなかったのだよな。でも先日プログラマさんの書いたコラムとか読んでて、throwっていう他の言語の構文を知ったことでとうとう理解できた。

例外って何?
プログラム実行中に起こる、想定外の現象のこと。ほんでプログラム実行中に起こる想定外っつったらエラーのことだから、まあエラーのことだ。以下のように書き下したらよく分かったよ。
例外 -> 想定外 -> プログラムにおける想定外とは? -> エラー

まあその、ドシロートっていうのはこういう用語ひとつでも引っかかっちゃうものなのですよ。pyてょんちゃんが「Exception!!」って叫んだら、「作者のお前が想定してなかったことが起きたぜ!」って意味になるわけだね。

raiseって何?
raiseは例外を生み出してひとつ上の階層に投げる呪文だ。いやエラーなんて放っておいても起こるんだから…なんでわざわざ自分で作るわけ!? ってのがオライリー読んだときは疑問で理解ができなかったんだけど、python的にはエラーじゃないけど俺的にはエラーになって欲しいって状況を想定したら理解できた。
def foo(flag):
    if not flag:
        raise Exception('Falseが来たから俺の都合で終了するよ。')
    return flag

foo(False)

実行すると以下のようになる。

Traceback (most recent call last):
  File "/Users/.../py.py", line 11, in <module>
    foo(False)
  File "/Users/.../py.py", line 8, in foo
    raise Exception('Falseが来たから俺の都合で終了するよ。')
Exception: Falseが来たから俺の都合で終了するよ。

いやー、Exceptionてのは俺の手中にはない範疇のモノだと思っていたから、ちょっと感動ですね。

感動ついでに自作のExceptionを作ってみる
こんなの書いたらできたわ。
class MidoriException(Exception):
    def __init__(self, error_message='緑エラーだよ。'):
        self.error_message = error_message
    def __str__(self):
        return self.error_message

raise MidoriException()

実行すると以下。

Traceback (most recent call last):
  File "/Users/.../py.py", line 11, in <module>
    raise MidoriException()
__main__.MidoriException: 緑エラーだよ。

すげえMidoriExceptionなんつーのが表示されちゃったぜ。なにこれ面白いんだけど。python的にはエラーじゃないんだけど俺の都合でエラーにする箇所については俺オリジナルのExceptionクラスを使えば、結果を見たときわかりやすいかもしんない。

「上の階層に投げる」てのはどゆこと?
階層ってのは「関数の呼出元」と「呼ばれた関数」の層のことだ。「呼ばれた関数」側でraiseすると、生み出したExceptionは「呼出元」へ浮かび上がる。俺が浮かび上げるから raise なんだな。他の言語では throw っていう文が使われてるらしいけど、そっちでは「浮かび上げる」じゃなくて「投げる」っていう感覚なんだね。 ちょっと書いてみる。グローバル -> foo -> bar -> baz の順番で階層的に関数を呼び出して、第四階層であるbazでMidoriExceptionをraiseしてみるぜ。
# 第四階層
def baz():
    # 上の階層barにExceptionを能動的に浮かび上げ(raise)る。
    raise MidoriException('第四階層でエラーが発生しました。')

# 第三階層
def bar():
    # 浮かび上がってきたExceptionは勝手に上の階層fooに浮かび上がる。
    baz()

# 第二階層
def foo():
    # 同じく、勝手に上の階層に浮かび上がる。
    bar()

foo()

なお、fooの中でtryを書いてbarから浮かんできたExceptionを捕まえて、自分で改めてraiseしても同じ結果になるみたい。

def foo2():
    try:
        # ここで捕まえる。
        bar()
    except Exception as e:
        # 捕まえたのを浮かび上げる。
        raise e

てことはさ、普段エラーが起こっているときはその場で勝手にraiseが起きていると思ってよさげ。あーなるほどね!

「例外」が理解できなかったことについては上述した。いっぽう、raiseが理解できなかった理由は、raiseという言葉が指すものを初見で勘違いしたからだ。つまり、実際は「例外を生んで上の階層に浮かび上げる」ことを指すんだが、俺は「例外を生むこと」自体をraiseだと思っちゃったんだよ。こう、俺にはよくわからないpythonの内部から、Exceptionというものを持ち上げるっていうイメージ。それは間違ってた。「Exceptionというものを持ち上げる」のは Exception() だ。raiseをつけることで初めてそれが上層へ飛び上がる。で、まあそういう勘違いをしてたんだけど、他の言語のthrowっていう言葉だとそういう間違いをしようがないよな。しかも他の言語だと throw new Exception(); っていう書き方をしてたんだよ。「新しくExceptionてクラスのインスタンス作ってそれを投げるのね」ってスグわかった。で、pythonだとそういう構文はあるのかな? って考えて、ああ、raiseってこのことだったのか! と思い至れたってわけ。