Pythonの例外ハンドラはローカル変数のスコープを破壊する

dev.thanaism.com

pythonはやばい。理解に苦しむ挙動が多くてつらい。たとえば以下のコード

def inverse(a):
    if a == 0:
        raise ValueError('0で割ることはできません')
    return 1 / a

def main():
    e = 0
    try:
        e = inverse(2)
    except ValueError as e:
        print(e.args[0])

    print(e)

main()

これは何と出力されるでしょうか?

0.5

となります。これは問題ありません

ではmainをこうするとどうでしょう

def main():
    e = 0
    try:
        e = inverse(0)
    except ValueError as e:
        print(e.args[0])

    print(e)

inverse(0)が例外を投げるので、以下のように出力されると予想するのではないでしょうか

0で割ることはできません
0

違います、実際はこうなります

0で割ることはできません
Traceback (most recent call last):
  File "/Users/hisanori/hoge.py", line 17, in <module>
    main()
  File "/Users/hisanori/hoge.py", line 15, in main
    print(e)
          ^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value

未定義のローカル変数エラーが発生しました。関数内で定義したローカル変数と同じ名前の変数を例外ハンドラに使用し、実際に例外がスローされた場合、元のローカル変数の定義がなかった事にされてしまっています。例外発生時だけこんなエラーを出すなら例外が発生していない時も同じエラーにしてほしい・・・

JavaやJSなどでは例外ハンドラで新しいスコープが作られるので元のeと例外ハンドラのeは別の変数となり予想通りエラーメッセージと0が出力されます。Rubyの場合は例外ハンドラが新しいスコープを作らないらしくエラーメッセージが2回でます。

という感じに他の言語で培った常識が通じなかったりして、pythonにはあまり深入りしたくないという気持ちが強い