Python | ファイル操作など:例外の種類

Python
スポンサーリンク

概要(「例外」は失敗を安全に扱うための仕組み)

例外は、処理が想定外の状態になったときに「止める/切り替える」ための信号です。Python では try/except/else/finally で「失敗時の動き」を明示し、必要なら raise で「意図的に失敗」を発生させます。ファイル操作は外部要因(存在しない、権限なし、使用中)で失敗しやすいため、代表例外の見分け方と「安全な受け止め方」を覚えると、一気に実務品質へ近づきます。

try:
    with open("data.csv", "r", encoding="utf-8", newline="") as f:
        print(f.readline())
except FileNotFoundError:
    print("ファイルが見つかりません")
except PermissionError:
    print("ファイルにアクセスできません(権限)")
Python

例外処理の基本(ここが重要)

try/except/else/finally と raise の役割

try:
    # 失敗しうる処理(例:ファイルI/O、パース、ネット)
    ...
except SomeError:
    # 失敗時の代替(記録、再試行、メッセージなど)
    ...
else:
    # 例外が起きなかったときだけ実行(後続処理)
    ...
finally:
    # 成否に関係なく必ず実行(後片付け)
    ...
Python
  • 基本線: 失敗の可能性がある範囲を try に閉じ込め、起きうる例外を絞って except で受ける。
  • 設計の肝: 例外ごとに「ユーザーへ見せるメッセージ」「ログの詳細」「再試行の有無」を決めておく。
  • raise: 自作の検証に失敗した時など、意図的に例外を投げて上位へ判断を委ねる。
def must_positive(n: int) -> None:
    if n <= 0:
        raise ValueError("正の値が必要です")
Python

ファイル操作でよく出る例外の種類(実務頻度順の要点)

存在・パス・権限に関する例外

  • FileNotFoundError: 指定パスが存在しない(ファイル/親ディレクトリ)。
    対策の要点: 事前に親ディレクトリを作る、例外で「無ければスキップ」を許容。
from pathlib import Path
p = Path("out/report.txt")
p.parent.mkdir(parents=True, exist_ok=True)
with p.open("w", encoding="utf-8") as f:
    f.write("ready\n")
Python
  • PermissionError: 権限不足(読めない・書けない・ロック中)。
    対策の要点: ユーザーに権限確認を促す、別ディレクトリへの退避、リトライ設計。
  • IsADirectoryError / NotADirectoryError: 期待と逆の種別(ファイルのはずがディレクトリ、またはその逆)。
    対策の要点: os.path.isfile / is_dir のチェック、命名規約の徹底。
import os
path = "data"
if os.path.isdir(path):
    print("これはディレクトリです")
Python
  • FileExistsError: 新規作成(”x” モードや mkdir)で既存がある。
    対策の要点: 既存なら別名、タイムスタンプ付与、上書き方針を明確化。
try:
    with open("report.txt", "x", encoding="utf-8") as f:
        f.write("new\n")
except FileExistsError:
    print("既に存在します")
Python

文字コード・内容に関する例外

  • UnicodeDecodeError / UnicodeEncodeError: エンコーディング不一致で文字化け(読めない/書けない)。
    対策の要点: 原則 UTF-8 明示、unknown データは errors=”replace” で応急処置。
with open("unknown.txt", "r", encoding="utf-8", errors="replace") as f:
    text = f.read()  # 変換できない文字は置換
Python
  • JSONDecodeError(json.load 時): JSON の構文が壊れている/想定外。
    対策の要点: 例外でファイル名と位置を記録、元データ修正を優先、フェイルセーフに切替。
  • ValueError: パース・変換が失敗(例:int(“abc”))。
    対策の要点: バリデーション実施、デフォルト値やスキップ設計。

OS・I/O 全般の例外

  • OSError: OS 由来の広い失敗の親クラス(上記多くを包含)。
    対策の要点: 詳細例外でできるだけ受ける(広すぎる except はバグ隠しになりがち)。
  • IOError: 歴史的名称(現行は OSError 系)。古いコードで遭遇することあり。
try:
    with open("dev.txt", "rb") as f:
        f.read(1024)
except OSError as e:
    print("OS系で失敗:", e)
Python

深掘り(安全設計で守る・例外連鎖・TOCTOU)

事前準備と「後片付け」を with で自動化する

  • 基本線: open は常に with、ディレクトリは事前に mkdir。
  • 効果: 例外でも自動 close/リーク防止。中途半端な書き込みを減らす。
from pathlib import Path
p = Path("logs/app.log")
p.parent.mkdir(parents=True, exist_ok=True)
with p.open("a", encoding="utf-8", newline="\n") as f:
    f.write("started\n")
Python

TOCTOU(存在確認→削除/移動の間に外部変更)の落とし穴

  • 本質: os.path.exists の直後に他プロセスがファイルを動かすかも。
  • 対策: 最終判断は操作(remove/rename)側の例外で行う。存在確認は“参考程度”。
import os
try:
    os.remove("target.tmp")  # 最終判断はここ
except FileNotFoundError:
    pass  # 既にないなら目的は達成
Python

例外連鎖(原因を失わないメッセージ設計)

  • ポイント: except で握りつつ、ログには元例外(repr(e))を残す。
  • 効果: 調査・再現性が上がる。ユーザーには簡潔、ログは詳細で二層化。

実務のハンドリングパターン(準備・記録・復旧)

ユーザー向けメッセージと技術ログの分離

  • ユーザー: 「ファイルが開けません。場所や権限を確認してください」
  • ログ: 例外種類、スタック、パス、時刻、実行環境。
import logging
logging.basicConfig(filename="app.log", level=logging.INFO)

def load_text(path: str) -> str | None:
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except (FileNotFoundError, PermissionError) as e:
        logging.exception("load_text failed: %s", path)
        return None
Python

フェイルセーフ(安全側へ倒す)

  • 読み取り失敗: スキップして続行、欠損として扱う。
  • 書き込み失敗: テンポラリ保存→再試行、別ディレクトリへフォールバック。
import os
from pathlib import Path

def atomic_write(path: Path, data: str) -> bool:
    tmp = path.with_suffix(path.suffix + ".tmp")
    try:
        tmp.write_text(data, encoding="utf-8")
        os.replace(tmp, path)
        return True
    except OSError:
        return False
Python

例外を絞る(広すぎる except は避ける)

  • 悪例: except Exception: で全て飲み込む(バグが隠れる)。
  • 良例: 想定済みの具体例外だけを受け、未知は上位へ伝える。

例題で身につける(定番から一歩先まで)

例題1:読み込みの安全化(存在・権限・文字コード)

def read_text_safe(path: str) -> str | None:
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        print("ファイルが存在しません:", path)
    except PermissionError:
        print("権限がありません:", path)
    except UnicodeDecodeError:
        print("文字コード不一致:", path)
    return None
Python

例題2:CSV の頑健な処理(壊れた行は記録して前進)

import csv

def total_qty(path: str) -> int:
    total = 0
    try:
        with open(path, "r", encoding="utf-8", newline="") as f:
            for i, row in enumerate(csv.reader(f), start=1):
                try:
                    total += int(row[1])
                except (IndexError, ValueError) as e:
                    print(f"WARN line={i} {e} row={row}")
    except (FileNotFoundError, PermissionError) as e:
        print("I/O 失敗:", e)
    return total
Python

例題3:新規のみ生成(誤上書き防止)

def create_once(path: str, content: str) -> bool:
    try:
        with open(path, "x", encoding="utf-8", newline="\n") as f:
            f.write(content)
        return True
    except FileExistsError:
        print("既存のため作成しません:", path)
        return False
Python

例題4:JSON 読み(壊れた構文を検知してフェイルセーフ)

import json

def load_json_safe(path: str) -> dict | list | None:
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        print("ファイルが見つかりません:", path)
    except PermissionError:
        print("権限がありません:", path)
    except json.JSONDecodeError as e:
        print("JSON構文エラー:", e)
    return None
Python

まとめ

例外は「外部の不確実性」を安全に受け止めるための仕組みです。ファイル操作では、存在(FileNotFoundError)、権限(PermissionError)、種別不一致(IsADirectoryError/NotADirectoryError)、衝突(FileExistsError)、文字コード(UnicodeDecodeError)を軸に考え、try/except を「必要十分な範囲」で掛けるのが基本線。with で後片付けを自動化し、TOCTOU を例外ベースで回避、ユーザー向けメッセージと技術ログを分離する。未知の失敗は握り過ぎずに上位へ伝え、フェイルセーフで運用を止めない。これらを習慣化すれば、初心者でも例外に強い、堅牢なファイル処理を書けます。

タイトルとURLをコピーしました