第1部 Python基礎編
第6章 ファイル操作と例外処理
実務データはファイルの中にあります。ファイルを安全に読み書きする方法と、エラーで止まらない頑丈なプログラムの書き方を学びます。
🎯 この章で学ぶこと
open()とwith文によるファイルの読み書き- CSVファイルの扱い方
try/exceptによる例外処理- 「安全に失敗する」プログラムの考え方
6.1 ファイルを読む — with open()
まず、練習用のログファイル access.log を作りましょう(エディタで以下を保存)。
2026-07-08 09:00:01 LOGIN OK user=tanaka ip=192.168.1.20
2026-07-08 09:00:15 LOGIN FAILED user=admin ip=203.0.113.9
2026-07-08 09:00:18 LOGIN FAILED user=admin ip=203.0.113.9
2026-07-08 09:01:02 LOGIN OK user=suzuki ip=192.168.1.31
2026-07-08 09:01:30 LOGIN FAILED user=root ip=203.0.113.9
読み込みの基本形はこれです。
# read_log.py
with open("access.log", encoding="utf-8") as f:
for line in f:
line = line.strip() # 行末の改行を除去
if "FAILED" in line:
print(line)
ポイントを整理します。
with open(...) as f:— ファイルを開き、ブロックを抜けると自動で閉じる。閉じ忘れによるリソースリークを防ぐため、ファイルは必ずwith文で開くと決めてしまいましょうencoding="utf-8"— 文字コードの明示。日本語環境の文字化けトラブルを防ぐfor line in f:— 1行ずつ読むので、巨大なログでもメモリを圧迫しないstrip()— 行末の改行文字や余分な空白を除去する定番の下ごしらえ
6.2 ファイルに書く
# write_report.py
findings = ["adminへの失敗3回", "root試行1回"]
with open("report.txt", "w", encoding="utf-8") as f:
f.write("== 点検レポート ==\n")
for item in findings:
f.write(f"- {item}\n")
第2引数がモードです:"r" 読み込み(省略時)、"w" 書き込み(既存内容を消して上書き)、"a" 追記。
"w" で開いた瞬間、既存のファイル内容は消えます。「上書きして良いファイルか」を確認してから書く癖をつけましょう。監査ログのような改ざんされては困るファイルへの追記は "a" を使います。
6.3 CSVファイルを扱う
IT部門で最も出会うデータ形式がCSV(カンマ区切り)です。標準ライブラリの csv モジュールを使います。
# accounts.csv
username,department,last_login
tanaka,情シス,2026-07-01
suzuki,営業,2025-11-20
sato,経理,2026-06-28
import csv
with open("accounts.csv", encoding="utf-8") as f:
reader = csv.DictReader(f) # 1行目をヘッダとして辞書で読む
for row in reader:
if row["last_login"] < "2026-01-01":
print(f"長期未ログイン: {row['username']} ({row['department']})")
長期未ログイン: suzuki (営業)
DictReader を使うと各行が辞書になり、row["username"] のように列名でアクセスできます(第4章の辞書の知識がそのまま活きます)。
6.4 例外処理 — try / except
存在しないファイルを開こうとすると、プログラムはエラーで停止します。
>>> open("nai.log")
Traceback (most recent call last):
...
FileNotFoundError: [Errno 2] No such file or directory: 'nai.log'
この「実行中に起きるエラー」を例外(exception)と呼びます。try / except を使うと、例外が起きたときの対応をプログラム自身に用意させられます。
try:
with open("access.log", encoding="utf-8") as f:
lines = f.readlines()
except FileNotFoundError:
print("ログファイルが見つかりません。パスを確認してください")
lines = []
print(f"{len(lines)} 行読み込みました")
第2章の宿題だった「数値変換の安全な受け取り」もこれで解決できます。
def read_int(prompt):
"""数値が入力されるまで聞き直す安全な入力関数"""
while True:
text = input(prompt)
try:
return int(text)
except ValueError:
print("数値を入力してください")
age = read_int("年齢: ")
print(f"{age} 歳ですね")
except の書き分けと finally
try:
with open("data.csv", encoding="utf-8") as f:
value = int(f.readline())
except FileNotFoundError:
print("ファイルがありません")
except ValueError:
print("1行目が数値ではありません")
except Exception as e:
# 想定外の例外。内容を記録して調査できるようにする
print(f"想定外のエラー: {e}")
finally:
print("処理を終了します") # 成功でも失敗でも必ず実行される
except Exception: pass のように例外を黙って無視するコードは絶対に書かないでください。エラーが起きた事実ごと消えるため、障害にも侵入の兆候にも気づけなくなります。例外は「捕まえたら、記録するか、ユーザーに伝えるか、対処する」のいずれかが必須です。
攻撃者はわざとエラーを起こして、返ってくるメッセージからシステムの内部情報(ファイルパス、SQL文、ライブラリのバージョンなど)を収集します。これをエラーベースの情報収集と呼びます。原則は「利用者には簡潔なエラーメッセージ、詳細はサーバー側のログにだけ記録」。Tracebackを画面にそのまま出すWebアプリは、攻撃者に設計図を配っているのと同じです。
6.5 実践 — 頑丈なログ集計スクリプト
この章の内容を組み合わせた、実務スタイルの小さなスクリプトです。
# failed_report.py — ログイン失敗をユーザー別に集計してファイル出力
import csv
def load_lines(path):
"""ログを読み込む。無ければ空リスト(プログラムは止めない)"""
try:
with open(path, encoding="utf-8") as f:
return [line.strip() for line in f]
except FileNotFoundError:
print(f"[警告] {path} が見つかりません")
return []
def count_failed_by_user(lines):
counts = {}
for line in lines:
if "FAILED" not in line:
continue
for part in line.split(" "):
if part.startswith("user="):
user = part.replace("user=", "")
counts[user] = counts.get(user, 0) + 1
return counts
lines = load_lines("access.log")
counts = count_failed_by_user(lines)
with open("failed_report.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["user", "failed_count"])
for user, n in sorted(counts.items(), key=lambda x: x[1], reverse=True):
writer.writerow([user, n])
print(f"{len(counts)} ユーザー分のレポートを出力しました")
関数分割(第5章)、辞書での集計(第4章)、ファイルとCSV、例外処理──ここまでの全部が入っています。写経して動かしてみてください。
まとめ
- ファイルは必ず
with open(..., encoding="utf-8")で開く - CSVは
csv.DictReaderで列名アクセスが便利 - 実行時エラー=例外は
try / exceptで対処。種類ごとに書き分ける - 例外の握りつぶしは厳禁。記録・通知・対処のいずれかを必ず行う
- 詳細なエラー情報は利用者に見せず、ログにだけ残す
練習問題
6.1で作った access.log を読み込み、「FAILEDの行だけ」を failed_only.log という別ファイルに書き出すプログラムを書いてください。
解答を見る
with open("access.log", encoding="utf-8") as src:
failed_lines = [line for line in src if "FAILED" in line]
with open("failed_only.log", "w", encoding="utf-8") as dst:
dst.writelines(failed_lines)
print(f"{len(failed_lines)} 行を書き出しました")
ユーザーに「1〜65535のポート番号」を入力させ、数値でない・範囲外の場合は聞き直す関数 read_port() を書いてください。
解答を見る
def read_port():
"""1〜65535のポート番号が入力されるまで聞き直す"""
while True:
text = input("ポート番号 (1-65535): ")
try:
port = int(text)
except ValueError:
print("数値を入力してください")
continue
if 1 <= port <= 65535:
return port
print("範囲外です")
print(read_port())
「型の検証」と「範囲の検証」の2段階チェックになっています。これが入力バリデーションの基本形です。
次のコードのセキュリティ上の問題点を2つ指摘してください。
try:
with open(filename) as f:
data = f.read()
except Exception:
pass
print(f"読み込み結果: {data}")
解答を見る
- 例外の握りつぶし —
except Exception: passにより、ファイルが無い・権限が無いなどの異常が起きても誰も気づけません。記録もされず、障害調査も不可能になります。 - 失敗時に未定義変数を参照 — 例外が起きた場合
dataは定義されないままprintで使われるため、今度はNameErrorで落ちます。「エラー処理のつもりが別のエラーを生む」典型例です。
修正例:例外の種類を絞って捕捉し、メッセージを記録し、data に安全な初期値を与える(または処理を中断する)。