WhirlwindTourOfPython
pythono

The Japanese translation of "A Whirlwind Tour of Python" - Errors and Exceptions

http://nbviewer.jupyter.org/github/jakevdp/WhirlwindTourOfPython/blob/master/09-Errors-and-Exceptions.ipynb

エラーと例外

プログラマとしてあなたのスキルが問題ないとしても、実質的にはコードの間違いをするでしょう。そのような間違いは三つの基本的な味があります:

  • Syntax errors: コードがPythonで有効でない場所でのエラー(たいていなおしやすい)
  • Runtime errors: 文法的に有効なコードが実行時に失敗するエラー、多分不正なユーザの入力のため(ときどきなおしやすい)
  • Semantic errors: 論理的なエラー:問題なしでコードが実行されるが、結果が期待していないものとなる(しばしば追跡して直すのが難しい) ここで実行時エラーできれいに扱う方法に集中してみることにしましょう。みているとおり、Pythonは実行エラーを、フレームワークを扱う例外で扱えます。

実行エラー

もしPythonでいかなるコードもしたことがあるなら、実行時エラーにでくわすことでしょう。それは多くの方法で発生します。

例えば、もし定義されていない変数に参照しようとすると、

In [3]:
print(Q)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-e796bdcf24ff> in <module>()
----> 1 print(Q)

NameError: name 'Q' is not defined
Or if you try an operation that's not defined:

In [4]:
1 + 'abc'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-aab9e8ede4f7> in <module>()
----> 1 1 + 'abc'

TypeError: unsupported operand type(s) for +: 'int' and 'str'

または、数学的に定義がわるい結果を計算しようとするかもしれません:

In [5]:
2 / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-5-ae0c5d243292> in <module>()
----> 1 2 / 0

ZeroDivisionError: division by zero

もしくはおそらく存在しないシーケンスの要素にアクセスしようとします:

In [6]:
L = [1, 2, 3]
L[1000]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-6-06b6eb1b8957> in <module>()
      1 L = [1, 2, 3]
----> 2 L[1000]

IndexError: list index out of range

各場合で、Pythonは発生するエラーを単に示すのではなく、エラーが発生した場所の確実な行にそって確かに間違ったことについての情報を含む、意味のある例外を親切にも吐き出していることに留意しましょう。
意味のあるエラーへのアクセスをもつことは、あなたのコードの問題の根元を追跡しようとするときに、非常に役に立ちます。

例外の捕捉: try and except

Pythonが与える実行時の例外の操作の主たるツールは、try...except節です。その基本構造はこれです:

In [7]:
try:
    print("this gets executed first")
except:
    print("this gets executed only if there is an error")
this gets executed first

二つ目のブロックは実行されないことに留意しましょう。これは最初のブロックがエラーを返さないためです。tryブロックで問題のある文をだしてみて、何が起こるか見てみましょう。

In [8]:
try:
    print("let's try something:")
    x = 1 / 0 # ZeroDivisionError
except:
    print("something bad happened!")
let's try something:
something bad happened!

ここでtry文の中でエラーがあげられたことがわかります(この場合、ZeroDivisionError)、エラーは捕捉されて、except文は実行されました。

一つの方法はしばしばコードの他の部分や関数の中でユーザの入力をチェックするために使われます。例えば、0除算を補足して多分 10^100 のような適当な大きい数値のようないくつかの他の値を返す関数をもつことを望むかもしれません。

In [9]:
def safe_divide(a, b):
    try:
        return a / b
    except:
        return 1E100
In [10]:
safe_divide(1, 2)
Out[10]:
0.5
In [11]:
safe_divide(2, 0)
Out[11]:
1e+100

このコードには微妙な問題があります、つまり、例外の他の型生じたとき何が起こるでしょうか?例えば、多分意図しないでこんなことがあります:

In [12]:
safe_divide (1, '2')
Out[12]:
1e+100

数値と文字列を割ることは、TypeErrorを生じ、あなたの熱心なコードが捕捉して、ZeroDivisionErrorと想定するのです。この理由から、明示的に例外を捕捉するのはいつもかなりより良いアイデアです。

In [13]:
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return 1E100
In [14]:
safe_divide(1, 0)
Out[14]:
1e+100
In [15]:
safe_divide(1, '2')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-2331af6a0acf> in <module>()
----> 1 safe_divide(1, '2')

<ipython-input-13-10b5f0163af8> in safe_divide(a, b)
      1 def safe_divide(a, b):
      2     try:
----> 3         return a / b
      4     except ZeroDivisionError:
      5         return 1E100

TypeError: unsupported operand type(s) for /: 'int' and 'str'

これは0除算エラーを唯一捕捉するだけです、そしてすべての他のエラーが修正されずにいきます。

例外発生:raise

Python言語の部分を使うとき、通知される例外をもつことにどんな価値があるかをみてきました。それは、あなたが書くコードの中で、通知される例外の使用を作ることと等価です、つまりコードのユーザ(まっさきにあなた自身です)がそれらのエラーを引き起こすことを形作れます。

あなたが自身の例外を発生する方法は、raise文を使います。例えば、

In [16]:
raise RuntimeError("my error message")
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-16-c6a4c1ed2f34> in <module>()
----> 1 raise RuntimeError("my error message")

RuntimeError: my error message

これが使いやすいかもしれない場所の例として、前に定義したフィボナッチ関数に戻ってみましょう。

In [17]:
def fibonacci(N):
    L = []
    a, b = 0, 1
    while len(L) < N:
        a, b = b, a + b
        L.append(a)
    return L

ここで一つの潜在的な問題は入力値がマイナスになり得ることです。これは現在関数で任意のエラーを起こすでしょう、しかしユーザがサポートされていないマイナスのNをユーザに知らせたいかもしれない。無効なパラメータ値からのステミングするエラーは、慣習的に、ValueErrorであることをあげることになります。

In [18]:
def fibonacci(N):
    if N < 0:
        raise ValueError("N must be non-negative")
    L = []
    a, b = 0, 1
    while len(L) < N:
        a, b = b, a + b
        L.append(a)
    return L
In [19]:
fibonacci(10)
Out[19]:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
In [20]:
fibonacci(-10)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-20-3d291499cfa7> in <module>()
----> 1 fibonacci(-10)

<ipython-input-18-01d0cf168d63> in fibonacci(N)
      1 def fibonacci(N):
      2     if N < 0:
----> 3         raise ValueError("N must be non-negative")
      4     L = []
      5     a, b = 0, 1

ValueError: N must be non-negative

今やなぜ入力が無効かを正確にユーザがわかっているので、それを扱うためにtry...exceptブロックを使うことさえできるでしょう。

In [21]:
N = -10
try:
    print("trying this...")
    print(fibonacci(N))
except ValueError:
    print("Bad value: need to do something else")
trying this...
Bad value: need to do something else

より深く例外へのダイブ

概して、走り込みたいかもしれないここでいくつかの他の概念を言及したい。それらを使う方法と理由、これらの概念についての詳細をみないかもしれないが、代わりに、あなた自身でより探索できるように、単にあなたにシンタックスを示します。

エラーメッセージへのアクセスすること

ときどき、try...except文の中で、それ自身のエラーメッセージで動作できるようにしたいでしょう。これはasキーワードでなされます:

In [22]:
try:
    x = 1 / 0
except ZeroDivisionError as err:
    print("Error class is:  ", type(err))
    print("Error message is:", err)
Error class is:   <class 'ZeroDivisionError'>
Error message is: division by zero

この様式で、さらにあなたの関数の例外の扱いを調整できます。

特別な例外の定義

組み込みの例外に加えて、クラスの継承を通して、個別の例外を定義することが可能です。例えば、もしValueErrorの特別な種類を欲しいなら、こうします:

In [23]:
class MySpecialError(ValueError):
    pass

raise MySpecialError("here's the message")
---------------------------------------------------------------------------
MySpecialError                            Traceback (most recent call last)
<ipython-input-23-92c36e04a9d0> in <module>()
      2     pass
      3 
----> 4 raise MySpecialError("here's the message")

MySpecialError: here's the message

これは、このエラーの型を唯一捕捉するtry...exceptブロックを使えることになります。

In [24]:
try:
    print("do something")
    raise MySpecialError("[informative error message here]")
except MySpecialError:
    print("do something else")
do something
do something else

よりカスタマイズされたコードを開発することで、これが使いやすいものとわかるかもしれません。

try...except...else...finally

tryとexceptに加えて、elseとfinallyキーワードがさらなる例外の扱いを調整するために使えます。基本的な構造はこれです:

In [25]:
try:
    print("try something here")
except:
    print("this happens only if it fails")
else:
    print("this happens only if it succeeds")
finally:
    print("this happens no matter what")
try something here
this happens only if it succeeds
this happens no matter what

ここでelseの有用性は明らかですが、finallyの地点はなんでしょうか?すなわち、finally節は本当になんであろうとも実行されます:たいていそれが、操作が終わったあとのある種の片付けをするために使われることをみます。

Refs.