Pythonでのエラー処理について自分なりにメモを取っていたものを公開します。
詳しくは以下のリンクを参照してください。
環境
Python 3.x
Errorとは?
エラーには (少なくとも) 二つのはっきり異なる種類があります。それは 構文エラー (syntax error) と 例外 (exception) です。
構文エラーは、Pythonの文法として間違っているものを指します。コードの外面的なエラーということもできます。Pythonはコードブロックをインデントにより表現するので、可読性が高く構文エラーを引き起こしにくい言語である気がします。
その一方で、例外はコードの内面的なエラーということができると思います。
たとえば、以下のコードはPythonのコード的には正しいものですが、数学的におかしなものですよね。
test = 10 / 0 # <= 0で除算することはできません!
このように、コードを実行したタイミングで起こる不具合が、例外と言えます。
Errorに立ち向かう
構文エラーは、インタープリターがプログラムを解釈する際に自動的に検出してくれるため、怒られた箇所を修正すれば良いです。
しかし、例外にはどのように対処すればよいのでしょうか?
Pythonでは、例外を処理するためにtry
やexcept
といった構文が用意されています。
具体例を見てみましょう。
try:
answer = 10 / 0
except ZeroDivisionError:
print("divided by zero!!!")
上記のコードには、ポイントが二つあります。
-
try
節のブロック内で、例外が起こりうるコードを実行する -
except
節のブロック内で、try節で起こりうる例外の型
を指定して、その場合の処理を実行する
この場合は、0による除算が行われたことを示す例外であるZeroDivisionError
がtry節から発生し、それをexcept ZeroDivisionError
以下で処理しています。
このように、例外を処理するためにはtry ~ except
を利用しましょう。
さまざまな利用ケース
関数の実行をサポートする
二つの数値を引数として受け取り、その商を返すdivide
関数を以下のように定義します。
def divide(x, y):
answer = x / y
return answer
一見良さそうです。しかし、いままでにみてきたように割り算には注意が必要です。
divide(10, 0)
などが実行された暁にはZeroDivisionError
が発生してしまいます。
第2引数として0がわたされる(文字通りの0ではなく計算の結果として0が渡されてしまう)ケースも考慮して、以下のように関数を改良してみましょう。
import math
def divide(x, y):
try:
answer = x / y
except ZeroDivisionError:
print("numbers cannot be divided by zero!")
answer = math.nan
return answer
これで0で割ってしまった場合も、一応はプログラムは動くようになりました!
ただし、引数として文字列
やタプル
など、想定しないものが渡された場合はどうすれば良いでしょうか...
ここではその答えは記述しませんが、以下のようなポイントを押さえて実装してみてください。
- 演算を正しく行えない場合、どのような例外の型が送出されるのか
- 例外に対する正しいフォロー(except節での処理)が必要になりそうか
あまり例外ばかりを気にしてしまうと、コードが膨れ上がって可読性を損なうこともあるので、ケースバイケースで考えましょう。
例外の発生の有無を問わない共通の処理をはさむ
例外が起こるにせよ、怒らないにせよ何らかの処理を共通して挟みたい場合があると思います。
具体例としては少し苦しいですが、以下のコードをみてみてください。
try:
answer = 10 / 0
except ZeroDivisionError as e:
answer = math.nan
finally:
print(answer)
finally
という新しいキーワードが出てきました。
このキーワードを利用することで、例外が起こった場合にも、起こらなかった場合にもfinally
節で指定した処理が行われることになります。
ちなみに、except
節でas e
としていますが、これは発生した例外オブジェクトをe
という変数で保持して、その後のブロック内で参照できるようにしているものです。
そのため、以下のようにコードを変更した場合、例外の内容を出力してくれたりします。
# 省略
except ZeroDivisionError as e:
print(type(e)) # => <class 'ZeroDivisionError'>
print(e.args) # => ('division by zero',)
print(e) # => division by zero
変数のスコープはどうなっているの?
Pythonでは、変数のスコープは基本的にコードのインデントにより決定されます。
それでは、以下のコードで定義されているam_i_visible
という変数は、一番したのprint()
において参照できるでしょうか?
try:
am_i_visible = "yes"
except:
do_something()
print(am_i_visible) # => ???
正解は、参照できます。yes
が出力されると思います。
あくまで、try
節がどこまでかを明確にするためにインデントを利用しているので、この場合は参照ができます。
Pythonはスコープが少しややこしいときがありますね...
以下のfor
文では、変数n
がグローバル変数になってしまいますし...
sum = 0
for n in range(1, 5):
sum = sum + n
print(n) # => 4
最後に
簡単ではありますが、Pythonのエラー処理についてまとめてみました。
Pythonの例外処理には、まだまだ触れるべきことがありますが、一旦ここでおわります。
自分自身まだまだPython歴が浅いので、アドバイスなどありましたらぜひコメント欄に書き込んでください。