例外の種類

このレッスンで作るもの — ファイルがまだ無い初回起動でも落ちない load_or_empty を作り、例外の種類を使い分けます。

例外の種類

前回は ValueError を受け止めました。例外はほかにもたくさんの種類があり、何が起きたのか によって名前が変わります。よく出会うものを並べます。

例外起きる場面
ValueError型は合っているが中身が不正。int("abc")
TypeError型そのものが違う。"a" + 1
FileNotFoundError開こうとしたファイルが無い
KeyError辞書に無いキーを取り出そうとした
IndexErrorリストの範囲外を指した
ZeroDivisionError0 で割った

名前を暗記する必要はありません。エラーメッセージの 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 ごと囲むのが自然な形です。

diagram (will load when visible)

種類を書き分ける意味

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 が渡されたときだけ先にファイルを作ります。学習環境にあらかじめファイルを置けないための工夫で、手元では読み込みの部分だけあれば十分です。

  1. initial_textNone でなければ、path にその内容を書き出す
  2. try の中で path を読み込みモードで開く
  3. 開けたら read の結果を splitlines で行のリストにして返す
  4. 開けなければ FileNotFoundError が起き、except に移る
  5. 空リスト [] を返す

第 2 章で学んだ None と、第 6 章のデフォルト引数がここで組み合わさっています。initial_text=None は「省略されたら何も書かない」という意味です。

よくある間違い

  1. except の種類を間違えるexcept ValueError では FileNotFoundError を受け止められません。関係のない種類を書くと、例外はそのまま素通りして止まります。
  2. trywith を入れ忘れるwith の外側だけ try で囲んでも意味がありません。open が呼ばれる場所が try の中に入っている必要があります。
  3. None の判定に == を使うinitial_text is not None と書きます。空文字も「渡された」として扱いたいので、if initial_text: では空文字が省略扱いになってしまいます。

except FileNotFoundError を書いたのに return を忘れて pass にしてしまうと、関数は None を返します。空リストとは別物なので、呼び出す側で for が回らずエラーになります。

やってみよう

関数 load_or_empty(path, initial_text=None) を実装してください。initial_textNone でなければ、まず path にその内容を書き出します。そのうえで path を読み込み、splitlines で行のリストにして返します。

ファイルが存在しないときは FileNotFoundError を受け止めて空リスト [] を返します。受け止める種類は FileNotFoundError だけにして、except Exception のような広い書き方は使わないでください。

要件

  1. 関数名は load_or_empty で、引数は path と initial_text の 2 つ。initial_text の既定値は None
  2. initial_text が None でなければ、先に path へ書き出す
  3. 読み込みは try で囲み、splitlines で行のリストにして返す
  4. except FileNotFoundError で受け止めて空リストを返す
  5. 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,娯楽"]

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.py
main.py
学習モード

メモ

例外の種類

⌘S で保存