リファクタリングをPythonで実践!基本手法とコード例・注意点まで
- 1.例外とエラーの違い
- 2.try-exceptの基本的な使い方
- 3.故意に例外を発生させる方法:raise
- 4.例外オブジェクトを取得する方法
- 5.複数の例外をキャッチする方法
- 6.すべての例外をキャッチする方法
- 7.例外をキャッチした後の処理
プログラム処理には文字の出力や計算といったものから、ファイルの読み書き、データベース接続処理、API接続処理など様々な種類があります。
その中であらかじめエラーの可能性がある処理には、例外処理(exception)を設定しておくとプログラムが中断することなく最後まで実行できます。
Pythonに限らずPHPやJavaScriptなど他の言語でも使用する「try文」の基礎をおさえておきましょう。
1.例外とエラーの違い
try文は、一定範囲のプログラムに対して設定します。その範囲を決める上で「例外」と「構文エラー」を棲み分けておく必要があります。ふたつの違いを確認しておきましょう。
①構文エラーとは?
構文エラーは、Pythonの文法にそぐわない、プログラムが中断するエラーです。
例えば、下記のような計算式があったとします。
a = '10'
b = 5
c = a-b
print(c)
結果
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-40511145d458> in <module>()
1 a = '10'
2 b = 5
----> 3 c = a-b
4 print(c)
TypeError: unsupported operand type(s) for -: 'str' and 'int'
aの数字が文字列であるため構文エラーが発生します。
②例外とは?
例外とは、文法上は正しいけれども、ユーザーの入力値やネットワーク等の環境により処理が実行できない場合のエラーを意味します。
例えば以下のように、ユーザーに入力を求める場面があるとします。
input_file_name = input ('ファイル名を入力してください')
file_name = open(input_file_name)
text = file_name.read()
print(text)
file_name.close()
プログラムを実行すると、ファイル名の入力が求められます。
このとき意図するファイル名を入力し、ファイルを正常に読み込めればいいのですが、意図しないファイル名を入力されたりファイルの読み込みに失敗するとエラーが発生します。
ファイル名を入力してくださいhello.py
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-28-a7eadf25e8b4> in <module>()
1 input_file_name = input ('ファイル名を入力してください')
----> 2 file_name = open(input_file_name)
3 text = file_name.read()
4 print(text)
5 file_name.close()
FileNotFoundError: [Errno 2] No such file or directory: 'hello.py'
(意図しないファイル名hello.pyが入力された場合のエラー内容)
意図しないファイル名「hello.py」は、現在のディレクトリに存在しないのでエラーが発生し、プログラムが中断されます。また入力値が正しかったとしても、100%ファイルを開き続けられる保証はありません。
ユーザーが「間違ったファイル名」を入力するかもしれない、「ファイルが開かない」かもしれない、などのエラーは事前に予測できると思います。
このような場面で例外処理のtry文を活用すると、エラーに対応できる信頼性の高いプログラムを提供できるでしょう。
(ファイルの存在確認については、「import os」 と関数「 os.path.exists()」を用いて確認することも可能です。)
2.try-exceptの基本的な使い方
例外処理を設定できるtry-exceptの使い方をご紹介します。
①基本構文
まずは基本構文を確認しましょう。
try:
エラーが発生するかもしれないプログラム
except:
例外発生時に行いたいプログラム
tryとexceptはセットで使用します。
最初にtryとexceptで囲まれた範囲のプログラムを実行し、正常に処理が完了すればexcept以下のプログラムはパスします。もしtryとexceptで囲まれた範囲で例外発生した場合は、except以下のプログラムを実行する、という流れです。
②サンプルプログラム
ファイルの読み込みを例にtry-exceptを使ってみます。
input_file_name = input ('ファイル名を入力してください')
try:
file_name = open(input_file_name)
text = file_name.read()
print(text)
file_name.close()
except:
print('該当するファイルはありません')
結果
ファイル名を入力してくださいhello.py
該当するファイルはありません
意図しないファイル名が入力されてもエラーが発生することなく、プログラムを正常に終了できたことが確認できます。
3.故意に例外を発生させる方法:raise
try文の作成において、めったに起きないエラーに対して例外処理を確認したい場合もあります。そんなときはraiseを使って、明示的にエラーを発生させることが可能です。
①raiseの基本構文
try:
raise 例外種類
except 例外種類:
例外処理のプログラム
tryとexceptの範囲にraiseを設定することで、意図的に例外処理を実行できます。
このraiseに例外の種類を割り当てることで、様々なパターンの例外処理に対して、適切に対応できるプログラムを作成できるのです。
②raiseのサンプルプログラム
ファイルの読み込みに失敗した場合の例外処理を、raiseを使って確認してみましょう。
input_file_name = input ('ファイル名を入力してください')
try:
with open(input_file_name) as f:
my_text = f.read()
print(my_text)
raise OSError
except OSError:
print('ファイルの読み込みに失敗しました')
正しいファイル名をhello.txtと仮定します。
ファイル名を間違って入力した場合の結果
ファイル名を入力してください.xyz.txt
ファイルの読み込みに失敗しました
ファイル名を正しく入力した場合の結果
ファイル名を入力してください.hello.txt
hello.txt内の文章が表示されます
ファイルの読み込みに失敗しました
raiseの後に書いているOSErrorは、システム関連のエラーを返した場合の例外種類になります。OSErrorの他にImportErrorやTypeErrorなど、全部で46種類の例外種類をキャッチアップすることが可能です。
4.例外オブジェクトを取得する方法
try文において発生するかもしれない例外を、例外の種類別に分けて例外処理することも可能です。
例えば、変数はあるか、データ型はあっているか、など複数のエラーが想定される場合は以下のように例外処理を記述できるでしょう。
a = '10'
b = 5
try:
c = a-b
except NameError:
print('変数が設定されていないようです')
except TypeError:
print('データ型が間違っているようです')
except Exception:
print('例外発生です')
結果
データ型が間違っているようです
上記の場合は、例外に対してprint処理のみですが、開発内容によっては例外を変数として扱いたい場合もあります。その場合は、
except TypeError as te:
と記述することで例外を変数(例外オブジェクト)として扱えるようになります。この場合は、変数名がteになりますね。
上記プログラムの例外オブジェクトを取得するパターンを以下に紹介します。
a = '10'
b = 5
try:
c = a-b
except NameError as ne:
print('変数が設定されていないようです')
except TypeError as te:
print('データ型が間違っているようです')
print(te)
except Exception as e:
print('例外発生です')
結果
データ型が間違っているようです
unsupported operand type(s) for -: 'str' and 'int'
neやteとして例外オブジェクトを取得することで、エラー内容を扱えますね。
5.複数の例外をキャッチする方法
「4.例外オブジェクトを取得する方法」のサンプルプログラムでも登場していますが、例外ごとに複数例外処理を設定することができます。
①複数の例外に対しそれぞれ別の処理を実行する
例外処理を細かく分けて制御したい場合は、exceptを複数記述できます。
サンプルプログラム
import requests
input_url = input ('URLを入力してください')
try:
r = requests.get(input_url, timeout=1)
except requests.Timeout as err:
print('ちょっと時間かかりますね、例外です')
print(err)
except Exception as e:
print('例外です')
(上記プログラムの実行には、あらかじめrequestsのインストールが必要です。)
結果
URLを入力してくださいhttp://httpbin.org/delay/2
ちょっと時間かかりますね、例外です
HTTPConnectionPool(host='httpbin.org', port=80): Read timed out. (read timeout=1)
上記の例では、最初に実行時間が1秒以内に終わるかを確認し、1秒を超える場合は「ちょっと時間が…」を出力します。また、1秒未満で処理は完了したけれども何かエラーがある場合は「例外です」を出力する内容となっています。
このように複数例外処理を記述する場合は、例外を判定するクラスの階層に注意が必要です。
例えば、階層を気にせず以下のように例外処理を記述すると意図しない結果を招きます。
import requests
input_url = input ('URLを入力してください')
try:
r = requests.get(input_url, timeout=1)
except Exception as e:
print('例外です')
except requests.Timeout as err:
print('ちょっと時間かかりますね、例外です')
print(err)
結果
URLを入力してくださいhttp://httpbin.org/delay/2
例外です
本当は処理時間の例外処理を制御したいのに、例外全般の処理が実行されています。
Python公式ドキュメント「組み込み例外」を参考に、下の階層から順番に例外処理を記述しましょう。
②複数の例外に対し同じ処理を実行する
ここまで複数exceptを設定する場合、例外ごとの処理を記述してきました。
しかし中には、「この例外とこの例外だけチェックしたい」という場合もあります。そのときはひとつのexceptに複数の例外クラスを設定できます。
サンプルプログラム
import requests
input_file_name = input ('ファイル名を入力してください')
try:
file_name = open(input_file_name)
text_url = file_name.read()
print(text_url)
file_name.close()
r = requests.get(text_url, timeout=3)
print(r.json())
except (OSError, requests.Timeout, ValueError) as e:
print('例外です')
print(e)
結果
ファイル名を入力してくださいsample.csv
https://query1.finance.yahoo.com/v8/finance/chart/AAPL?interval=1d
例外です
Expecting value: line 1 column 1 (char 0)
(実行環境によって結果は以下のようになる場合もあります。)
ファイル名を入力してくださいsample.csv
https://query1.finance.yahoo.com/v8/finance/chart/AAPL?interval=1d
例外です
[Errno Expecting value] Forbidden: 0
上記は、ユーザーにファイルを指定してもらい、その中に記載されているURLにアクセスし、結果を取得するプログラムになります。
「ファイル」「Webページへのアクセス」「アクセス結果の処理」とエラーになりそうな項目がいくつかありますね。
exceptの行で、タプル型として起こりうる例外を順番に記述しています。
その結果、「ファイルデータの取得」「Webページへ指定時間内にアクセス」は問題なく行われていますが、最後のレスポンスの解析で失敗し、ValueErrorとして例外処理が実行されていることがわかります。
このように、ひとつのexceptに複数の例外クラスを設定することでプログラムの可読性は向上します。
複数の例外をひとつのexceptにまとめる場合は、基本的には起こりうる例外の対象クラス順で左から書くことを勧めます。
6.すべての例外をキャッチする方法
例外別の制御は行わず、単に例外があるときとないときで処理を分けたい場面は多々あります。そのような場合は以下の2パターンで制御できます。
①except節から例外名を省略する
基本に戻りますが、try-exceptですべての例外をキャッチできます。
try:
a
except:
print('例外です')
結果
例外です
この方法だと例外が発生したかどうかは判断できますが、場合によってはなぜエラーになるか確認したいときもあるでしょう。その場合は、次の「基底クラス」を使用すると便利です。
②基底クラスを指定する
例外の内容を確認したい場合や複数の例外処理を設けるときなどは、「基底クラス」を利用します。
基底クラスは、例外クラス階層の上部になり、その中でも一番上のクラスはBaseExceptionになります。
サンプルプログラム
try:
a
except BaseException as be:
print('例外です')
print(be)
結果
例外です
name 'a' is not defined
一般的にはExceptionが使用されますが、参考までにBaseExceptionをご紹介しました。
7.例外をキャッチした後の処理
例外処理の基本はtry-exceptで、例外が起きなかった場合と起きた場合の2パターンです。しかし、例外の有無に関係なく処理を加えたい場合や、例外が発生したかどうかによって処理を分岐したい場合もあります。
try-exceptに付随できる処理を3つご紹介します。
①例外を無視する:pass
もしこの処理が実行できれば変数の内容を変えたい。そういった場面などではtry-exceptのexcept部分を省略したいこともあります。そのようなときは処理を省略するpassが使えます。
サンプルプログラム
x = 'HELLO JAVA'
try:
file_name = open('text.txt')
x = file_name.read()
file_name.close()
except Exception as e:
#print(e)
pass
print(x)
結果
HELLO JAVA
text.txtファイルの読み込みに成功すれば、変数xにはtext.txt内の文字がセットされますが、なければ初期設定のままの「HELLO JAVA」を使う、という内容になります。
例外処理のexcept内にpassがない場合は文法エラーになりますので、処理を省略したい場合はpassを使うようにしましょう。
②例外をキャッチしなかった場合の処理:else
例外が発生するかしないかわからない場合に、発生しなかった場合だけ処理を加えたいときもあるでしょう。try文の中に書くという方法もありますが、分けて管理する方がスマートな場合もあります。そのような場合はelseを使いましょう。
サンプルプログラム
text = ''
input_file_name = input ('ファイル名を入力してください')
try:
file_name = open(input_file_name)
text = file_name.read()
file_name.close()
except Exception as e:
print('該当するファイルはありません')
else:
print('「正しく入力してくれてありがとう🙏」')
print(text)
結果
ファイル名を入力してくださいmy-text.txt
「正しく入力してくれてありがとう🙏」
HELLO PYTHON
例外処理が発生しなかった場合はelse以下のプログラムが実行されます。例外が発生した場合は、else以下のプログラムは実行されません。
③終了時に必ず実行する場合の処理:finally
例外の発生有無に関係なく処理を加えたい場合は、finallyを使用すると便利です。
サンプルプログラム
a = '10'
b = 5
c = 0
try:
c = a-b
except NameError as ne:
print('変数が設定されていないようです')
except TypeError as te:
print('データ型が間違っているようです')
except Exception as e:
print('例外発生です')
else:
print('例外なし')
finally:
#いずれの場合でも変数cをデータベースに保存
print('try文終了')
結果
データ型が間違っているようです
try文終了
finallyを使わずに、try文の後にfinally内の内容を記述すれば処理することも可能ですが、tryで実行する内容はまとめて書いておく方が管理しやすいでしょう。
データベースへの保存や関数のreturn値をfinallyで管理できますね。