8. エラーと例外¶
これまでエラーメッセージについては簡単に触れるだけでしたが、チュートリアル中の例を自分で試していたら、実際にいくつかのエラーメッセージを見ていることでしょう。エラーには (少なくとも) 二つのはっきり異なる種類があります。それは 構文エラー (syntax error) と 例外 (exception) です。
8.1. 構文エラー¶
構文エラーは構文解析エラー (parsing error) としても知られており、Python を勉強している間に最もよく遭遇する問題の一つでしょう:
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
パーサは違反の起きている行を表示し、小さな「矢印」を表示して、行中でエラーが検出された最初の位置を示します。エラーは矢印の 直前の トークンでひき起こされています (または、少なくともそこで検出されています)。上記の例では、エラーは関数 print()
で検出されています。コロン (':'
) がその前に無いからです。入力がスクリプトから来ている場合は、どこを見ればよいか分かるようにファイル名と行番号が出力されます。
8.2. 例外¶
たとえ文や式が構文的に正しくても、実行しようとしたときにエラーが発生するかもしれません。実行中に検出されたエラーは 例外 (exception) と呼ばれ、常に致命的とは限りません。これから、Python プログラムで例外をどのように扱うかを学んでいきます。ほとんどの例外はプログラムで処理されず、以下に示されるようなメッセージになります:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
エラーメッセージの最終行は何が起こったかを示しています。例外は様々な型 (type) で起こり、その型がエラーメッセージの一部として出力されます。上の例での型は ZeroDivisionError
, NameError
, TypeError
です。例外型として出力される文字列は、発生した例外の組み込み名です。これは全ての組み込み例外について成り立ちますが、ユーザ定義の例外では (成り立つようにするのは有意義な慣習ですが) 必ずしも成り立ちません。標準例外の名前は組み込みの識別子です (予約語ではありません)。
残りの行は例外の詳細で、その例外の型と何が起きたかに依存します。
エラーメッセージの先頭部分では、例外が発生した実行コンテキスト (context) を、スタックのトレースバック (stack traceback) の形式で示しています。一般には、この部分にはソースコード行をリストしたトレースバックが表示されます。しかし、標準入力から読み取られたコードは表示されません。
組み込み例外 には、組み込み例外とその意味がリストされています。
8.3. 例外を処理する¶
例外を選別して処理するようなプログラムを書くことができます。以下の例を見てください。この例では、有効な文字列が入力されるまでユーザに入力を促しますが、ユーザがプログラムに (Control-C か、またはオペレーティングシステムがサポートしている何らかのキーを使って) 割り込みをかけてプログラムを中断させることができるようにしています。ユーザが生成した割り込みは、 KeyboardInterrupt
例外が送出されることで通知されるということに注意してください。
>>> while True:
... try:
... x = int(input("Please enter a number: "))
... break
... except ValueError:
... print("Oops! That was no valid number. Try again...")
...
try
文は下記のように動作します。
何も例外が発生しなければ、 except 節 をスキップして
try
文の実行を終えます。If an exception occurs during execution of the
try
clause, the rest of the clause is skipped. Then, if its type matches the exception named after theexcept
keyword, the except clause is executed, and then execution continues after the try/except block.If an exception occurs which does not match the exception named in the except clause, it is passed on to outer
try
statements; if no handler is found, it is an unhandled exception and execution stops with a message as shown above.
A try
statement may have more than one except clause, to specify
handlers for different exceptions. At most one handler will be executed.
Handlers only handle exceptions that occur in the corresponding try clause,
not in other handlers of the same try
statement. An except clause
may name multiple exceptions as a parenthesized tuple, for example:
... except (RuntimeError, TypeError, NameError):
... pass
A class in an except
clause is compatible with an exception if it is
the same class or a base class thereof (but not the other way around --- an
except clause listing a derived class is not compatible with a base class).
For example, the following code will print B, C, D in that order:
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
Note that if the except clauses were reversed (with except B
first), it
would have printed B, B, B --- the first matching except clause is triggered.
All exceptions inherit from BaseException
, and so it can be used to serve
as a wildcard. Use this with extreme caution, since it is easy to mask a real
programming error in this way! It can also be used to print an error message and
then re-raise the exception (allowing a caller to handle the exception as well):
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except BaseException as err:
print(f"Unexpected {err=}, {type(err)=}")
raise
Alternatively the last except clause may omit the exception name(s), however the exception
value must then be retrieved from sys.exc_info()[1]
.
The try
... except
statement has an optional else
clause, which, when present, must follow all except clauses. It is useful
for code that must be executed if the try clause does not raise an exception.
For example:
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
追加のコードを付け加えるのは try
節よりも else
節の方がよいでしょう。
なぜなら、そうすることで try
... except
文で保護されたコードから送出されたもの以外の例外を過って捕捉してしまうという事態を避けられるからです。
例外が発生するとき、例外は関連付けられた値を持つことができます。この値は例外の 引数 (argument) とも呼ばれます。引数の有無および引数の型は、例外の型に依存します。
The except clause may specify a variable after the exception name. The
variable is bound to an exception instance with the arguments stored in
instance.args
. For convenience, the exception instance defines
__str__()
so the arguments can be printed directly without having to
reference .args
. One may also instantiate an exception first before
raising it and add any attributes to it as desired.
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception instance
... print(inst.args) # arguments stored in .args
... print(inst) # __str__ allows args to be printed directly,
... # but may be overridden in exception subclasses
... x, y = inst.args # unpack args
... print('x =', x)
... print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
例外が引数を持っていれば、それらは処理されない例外のメッセージの最後の部分 (「詳細説明」) に出力されます。
Exception handlers don't just handle exceptions if they occur immediately in the try clause, but also if they occur inside functions that are called (even indirectly) in the try clause. For example:
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError as err:
... print('Handling run-time error:', err)
...
Handling run-time error: division by zero
8.4. 例外を送出する¶
raise
文を使って、特定の例外を発生させることができます。例えば:
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: HiThere
raise
の唯一の引数は送出される例外を指し示します。
これは例外インスタンスか例外クラス (Exception
を継承したクラス) でなければなりません。
例外クラスが渡された場合は、引数無しのコンストラクタが呼び出され、暗黙的にインスタンス化されます:
raise ValueError # shorthand for 'raise ValueError()'
例外が発生したかどうかを判定したいだけで、その例外を処理するつもりがなければ、単純な形式の raise
文を使って例外を再送出させることができます:
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere
8.5. 例外の連鎖¶
raise
文では from
を使い、例外を連鎖することができます。例えば:
# exc must be exception instance or None.
raise RuntimeError from exc
これは例外を変換するときに便利です。例えば:
>>> def func():
... raise ConnectionError
...
>>> try:
... func()
... except ConnectionError as exc:
... raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in func
ConnectionError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database
Exception chaining happens automatically when an exception is raised inside an
except
or finally
section. This can be
disabled by using from None
idiom:
>>> try:
... open('database.sqlite')
... except OSError:
... raise RuntimeError from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError
例外の連鎖の仕組みに関して、詳しくは 組み込み例外 を参照してください。
8.6. ユーザー定義例外¶
プログラム上で新しい例外クラスを作成することで、独自の例外を指定することができます (Python のクラスについては クラス 参照)。例外は、典型的に Exception
クラスから、直接または間接的に派生したものです。
Exception classes can be defined which do anything any other class can do, but are usually kept simple, often only offering a number of attributes that allow information about the error to be extracted by handlers for the exception.
ほとんどの例外は、標準の例外の名前付けと同様に、"Error" で終わる名前で定義されています。
多くの標準モジュールでは、モジュールで定義されている関数内で発生する可能性のあるエラーを報告させるために、独自の例外を定義しています。クラスについての詳細な情報は クラス 章で提供されています。
8.7. クリーンアップ動作を定義する¶
try
文にはもう一つオプションの節があります。この節はクリーンアップ動作を定義するためのもので、どんな状況でも必ず実行されます。例を示します:
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
もし finally
節がある場合、 try
文が終わる前の最後の処理を、 finally
節が実行します。 try
文が例外を発生させるか否かに関わらず、 finally
節は実行されます。以下では、例外が発生するという更に複雑なケースを議論します:
もし
try
文の実行中に例外が発生したら、その例外はexcept
節によって処理されるでしょう。もしその例外がexcept
節によって処理されなければ、finally
節が実行された後に、その例外が再送出されます。except
節またはelse
節の実行中に例外が発生することがあり得ます。その場合も、finally
節が実行された後に例外が再送出されます。If the
finally
clause executes abreak
,continue
orreturn
statement, exceptions are not re-raised.もし
try
文がbreak
文、continue
文またはreturn
文のいずれかに達すると、そのbreak
文、continue
文またはreturn
文の実行の直前にfinally
節が実行されます。もし
finally
節がreturn
文を含む場合、返される値はtry
節のreturn
文ではなく、finally
節のreturn
文によるものになります。
例えば:
>>> def bool_return():
... try:
... return True
... finally:
... return False
...
>>> bool_return()
False
より複雑な例:
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
見てわかるとおり、 finally
節はどの場合にも実行されています。
文字列で割り算をすることで発生した TypeError
は except
節で処理されていないので、 finally
節実行後に再度送出されています。
実世界のアプリケーションでは、 finally
節は(ファイルやネットワーク接続などの)外部リソースを、利用が成功したかどうかにかかわらず解放するために便利です。
8.8. 定義済みクリーンアップ処理¶
オブジェクトのなかには、その利用の成否にかかわらず、不要になった際に実行される標準的なクリーンアップ処理が定義されているものがあります。以下の、ファイルをオープンして内容を画面に表示する例をみてください。
for line in open("myfile.txt"):
print(line, end="")
このコードの問題点は、コードの実行が終わった後に不定の時間ファイルを開いたままでいることです。これは単純なスクリプトでは問題になりませんが、大きなアプリケーションでは問題になりえます。 with
文はファイルのようなオブジェクトが常に、即座に正しくクリーンアップされることを保証します。
with open("myfile.txt") as f:
for line in f:
print(line, end="")
この文が実行されたあとで、たとえ行の処理中に問題があったとしても、ファイル f は常に close されます。ファイルなどの、定義済みクリーンアップ処理を持つオブジェクトについては、それぞれのドキュメントで示されます。