プログラム書いている人で、必ずといっても過言ではないくらい躓きがちなものが、エラー、例外処理だと思います。
今回はPythonにおける例外処理の基礎についてまとめました。
本記事のコードはすべて、Python 3.7.6で実行しています。
エラー、例外
Pythonなどのプログラム言語には、例外(Exception)とエラー(Error)言った概念があります。しばしばこれらの違いを問われますが、両者の違いを明確には定義できないらしいです。 本記事ではそれぞれ以下のように扱おうと思います。
- エラー
- プログラムが実行不可能な状態であること(Syntax Error)
- プログラムが異常終了する状態であること
- 例外
- プログラム内で発生した対処が必要な状態のこと
SyntaxError
以下のように、関数の後ろに:
がないなど、構文エラーの場合、プログラムは実行されず、異常終了。エラーを出力します。
def hello() return 'Hello' print(hello())
File "truefly.py", line 1 def hello() ^ SyntaxError: invalid syntax
例外
例えば以下のようなゼロ除算をしたとき、プログラムは2行目でZeroDivisionError
という例外を発生させます。ZeroDivisionError
は例外クラスと呼ばれるものです。例外クラスは、Exception
、KeyError
、TypeError
などの、Pythonで元々使える組み込み例外と、後述する自作の例外クラスがあります。
Pythonはこの例外クラスを返すことで、どのような例外が発生したのかを判別することができます。
def divide(n, m): print(n / m) divide(10, 0)
Traceback (most recent call last): File "truefly.py", line 4, in <module> divide(10, 0) File "truefly.py", line 2, in divide print(n / m) ZeroDivisionError: division by zero
では、このような例外が発生したときの対処法について考えていきましょう。
例外処理
try exceptで例外が発生時の処理を書く
プログラムが例外を発生させたときに実行するときの処理のことを、例外処理といいます。そのままですね。例えば除算する処理でゼロ除算が発生した場合、何も対処しないと、ZeroDivisionError
が発生して処理が異常終了しますが、
実際のプロダクトでは、
「ゼロで割ることはできません!」
といった文言を出力して、再度入力を促したいですよね。
そういった例外処理をするとき、Pythonでは、try
、except
を使います。
def divide(n, m): try: print(n / m) except: print('エラーが発生しました') divide(10, 0)
エラーが発生しました
try
の中に例外が発生しうる処理を書き、except
の中に、try
内で例外が発生した時に実行する処理を書きます。この書き方の場合、どんな例外が発生した場合でも、except
内の処理が実行されます。exceptの後ろに、検知したい例外を書くことで、特定の例外が発生した時に例外処理することもできます。
def divide(n, m): try: print(n / m) except ZeroDivisionError: print('エラーが発生しました') divide(10, 0) divide(10, 'zero')
エラーが発生しました Traceback (most recent call last): File "truefly.py", line 8, in <module> divide(10, 'zero') File "truefly.py", line 3, in divide print(n / m) TypeError: unsupported operand type(s) for /: 'int' and 'str'
最初のdivide(10, 0)
ではZeroDivisionError
が発生しますが、無事例外処理がされています。その後のdivide(10, 'zero')
では、TypeError
が発生したため、例外処理がされず、エラーとなりました。
except
は、1つのtry
に対して、複数書くこともできます。また、except 例外クラス as 変数名
と書くことで、例外処理内で例外オブジェクトを利用することもできます。
def divide(n, m): try: print(n / m) except ZeroDivisionError as e: print('ゼロ除算のエラー') print(e) except TypeError as e: print('型のエラー') print(e) divide(10, 2) divide(10, 0) divide(10, 'zero')
5.0 ゼロ除算のエラー division by zero 型のエラー unsupported operand type(s) for /: 'int' and 'str'
try except elseで例外が発生しなかったときの処理を書く
except
は例外が発生したときの処理を書くことができますが、例外が発生しなかったときの処理を書きたいときは、
try except else
を使います。
def divide(n, m): ans = 0 try: ans = n / m print(ans) except ZeroDivisionError as e: print('ゼロ除算のエラー') print(e) finally: print('処理を終了します') divide(10, 0) divide(10, 2) divide(10, 'zero')
ゼロ除算のエラー division by zero 処理を終了します 答え 5.0 処理を終了します
ZeroDivisionError
発生時はexcept
句内の処理を、それ以外の場合はelse
句内の処理を実行してくれるようになりました。
個人的には例外が発生しなかったら、try句の処理が最後まで走るので、わざわざelse
を書くことは多くはないかなと思っています。
try except finallyで例外が発生したとき、しなかったときの共通処理を書く
例外が発生したとき、例外が発生しなかったとき例外が発生したときの書き方はわかりましたが、例外が発生したとき、しなかったときの共通処理もあると便利ですよね。そんなときはtry except finally
を使います。
def divide(n, m): ans = 0 try: ans = n / m print(ans) except ZeroDivisionError as e: print('ゼロ除算のエラー') print(e) finally: pass print('処理を終了します') divide(10, 0) divide(10, 2) divide(10, 'zero')
ゼロ除算のエラー division by zero 処理を終了します 5.0 処理を終了します 処理を終了します Traceback (most recent call last): File "main.py", line 14, in <module> divide(10, 'zero') File "main.py", line 4, in divide ans = n / m TypeError: unsupported operand type(s) for /: 'int' and 'str'
divide()
の1回目の呼び出しはZeroDivisionError
が発生しているため、except
、finally
句内の処理を実行されています。
2回目の呼び出しは例外が発生していないため、try
句が最後まで処理され、finally
句内の処理も実行されています。
そして3回目の処理は、TypeError
が発生しているため、try
句が最後まで処理されず、finally
句内の処理のみを実行して、異常終了しました。
DBのコネクションクローズや、ロールバック処理など、正常時でも異常時でも必ず実行したい処理をfinally
句内に書けるので便利です。
まとめ
以上、Pythonのエラー、例外と、例外処理について学びました。
プログラムはどんなに成熟したプログラマーがテストを重ねたとしても、通信障害だったり、実装時に意図していなかった操作などが原因で、
こういった例外が発生することは往々にしてあります。そういったケースにも対応できるよう、予め例外処理を実装しておき、可用性の高いシステムをつくりましょう。
次回は例外の発生方法と、例外クラスについてまとめようと思います。