例外の種類
このレッスンで作るもの — ファイルがまだ無い初回起動でも落ちない
load_or_emptyを作り、例外の種類を使い分けます。
例外の種類
前回は ValueError を受け止めました。例外はほかにもたくさんの種類があり、何が起きたのか によって名前が変わります。よく出会うものを並べます。
| 例外 | 起きる場面 |
|---|---|
ValueError | 型は合っているが中身が不正。int("abc") |
TypeError | 型そのものが違う。"a" + 1 |
FileNotFoundError | 開こうとしたファイルが無い |
KeyError | 辞書に無いキーを取り出そうとした |
IndexError | リストの範囲外を指した |
ZeroDivisionError | 0 で割った |
名前を暗記する必要はありません。エラーメッセージの 1 行目にそのまま出るので、出てから読めば十分です。大事なのは、受け止めるときに種類を指定する ことです。
例外の名前は、原因を説明する短いラベルです。読めば直し方の見当が付く、という点でエラーは味方です。
初回起動という現実の問題
おこづかい帳を起動したとき、まず保存ファイルを読み込みたくなります。ところが 一番最初の起動ではファイルがまだ存在しません。
with open("kakeibo.txt", "r", encoding="utf-8") as f:
text = f.read()この行は FileNotFoundError で止まります。メッセージは No such file or directory と、探したパスまで教えてくれます。
ここで欲しい動きは「ファイルが無いなら、記録は 0 件として始める」です。エラーで止めるのではなく、空リストを返して先に進みます。
try:
with open(path, "r", encoding="utf-8") as f:
return f.read().splitlines()
except FileNotFoundError:
return []try の中に with を丸ごと入れています。open の時点で例外が出るので、with ごと囲むのが自然な形です。
種類を書き分ける意味
except Exception と書けば、ほぼすべての例外を一手に受け止められます。楽に見えますが、これは避けます。
たとえば open のパスに書き間違いがあってディレクトリを指してしまった場合、起きるのは IsADirectoryError です。これを「ファイルが無い」と同じ扱いにして空リストを返すと、設定ミスに気づけないまま記録が消えたように見えます。
受け止める種類を FileNotFoundError に絞っておけば、想定していない例外はそのまま表に出ます。表に出れば直せます。
複数の種類をまとめて受け止めたいときは、かっこで並べます。
except (ValueError, TypeError):
return 0種類ごとに違う対応をしたいときは、except を続けて書きます。上から順に照合され、最初に当てはまったものだけが実行されます。
try:
number = int(text)
except ValueError:
number = 0
except TypeError:
number = -1広く捕まえるほどプログラムは静かになりますが、静かなプログラムは壊れていても気づけません。捕まえる範囲は必要最小限にします。
動きを追ってみる
この演習の load_or_empty(path, initial_text=None) は、initial_text が渡されたときだけ先にファイルを作ります。学習環境にあらかじめファイルを置けないための工夫で、手元では読み込みの部分だけあれば十分です。
initial_textがNoneでなければ、pathにその内容を書き出すtryの中でpathを読み込みモードで開く- 開けたら
readの結果をsplitlinesで行のリストにして返す - 開けなければ
FileNotFoundErrorが起き、exceptに移る - 空リスト
[]を返す
第 2 章で学んだ None と、第 6 章のデフォルト引数がここで組み合わさっています。initial_text=None は「省略されたら何も書かない」という意味です。
よくある間違い
exceptの種類を間違える —except ValueErrorではFileNotFoundErrorを受け止められません。関係のない種類を書くと、例外はそのまま素通りして止まります。tryにwithを入れ忘れる —withの外側だけtryで囲んでも意味がありません。openが呼ばれる場所がtryの中に入っている必要があります。Noneの判定に==を使う —initial_text is not Noneと書きます。空文字も「渡された」として扱いたいので、if initial_text:では空文字が省略扱いになってしまいます。
except FileNotFoundError を書いたのに return を忘れて pass にしてしまうと、関数は None を返します。空リストとは別物なので、呼び出す側で for が回らずエラーになります。
やってみよう
関数 load_or_empty(path, initial_text=None) を実装してください。initial_text が None でなければ、まず path にその内容を書き出します。そのうえで path を読み込み、splitlines で行のリストにして返します。
ファイルが存在しないときは FileNotFoundError を受け止めて空リスト [] を返します。受け止める種類は FileNotFoundError だけにして、except Exception のような広い書き方は使わないでください。
要件
- 関数名は load_or_empty で、引数は path と initial_text の 2 つ。initial_text の既定値は None
- initial_text が None でなければ、先に path へ書き出す
- 読み込みは try で囲み、splitlines で行のリストにして返す
- except FileNotFoundError で受け止めて空リストを返す
- except Exception のように広く捕まえる書き方はしない
入出力例
load_or_empty("/tmp/kakeibo_missing_9x8y7z.txt") → []
load_or_empty("/tmp/kakeibo_load_1.txt", "りんご,120,食費
みかん,80,食費") → ["りんご,120,食費","みかん,80,食費"]
load_or_empty("/tmp/kakeibo_load_2.txt", "") → []
load_or_empty("/tmp/kakeibo_load_3.txt", "ノート,200,日用品") → ["ノート,200,日用品"]
load_or_empty("/tmp/kakeibo_missing_abc123.txt") → []
load_or_empty("/tmp/kakeibo_load_4.txt", "バス,220,交通費
映画,1800,娯楽
") → ["バス,220,交通費","映画,1800,娯楽"]