エラーハンドリングを伴う安全なファイル操作
ファイルの存在と権限を確認し、IOエラーを適切に処理します。
「エラーハンドリングを伴う安全なファイル操作」はCoddyKit上の無料AI Agentsレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Agents学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Agentsコースには全4レッスンが含まれています。
安全なファイル操作が重要な理由
エージェントのファイル操作は、ファイルが存在しない、エージェントに権限がない、パスがディレクトリを指している、書き込みの途中でディスク容量が不足するなど、さまざまな理由で失敗する可能性があります。ファイルエラーでクラッシュするエージェントは、途中までの出力や壊れた状態を残してしまいます。適切なチェックとエラーハンドリングを用いた防御的プログラミングによって、エージェントの堅牢性を高められます。
from pathlib import Path
# Unsafe: crashes with FileNotFoundError
# content = Path('missing.txt').read_text()
# Safe: check first
path = Path('config.json')
if path.exists():
content = path.read_text(encoding='utf-8')
print('Loaded config')
else:
print(f'Config not found at {path.resolve()}')
content = '{}' # use defaultPath.exists() と Path.is_file()
ファイルを読み込む前に、そのファイルが存在し、かつ実際に通常のファイルであること(ディレクトリ、ディレクトリへのシンボリックリンク、特殊ファイルではないこと)を確認してください。Path.exists()はあらゆるファイルシステムオブジェクトに対してTrueを返しますが、Path.is_file()は通常のファイルに対してのみTrueを返します。
from pathlib import Path
path = Path('data/report.csv')
# Chain of checks
if not path.exists():
print(f'Not found: {path}')
elif not path.is_file():
print(f'Not a regular file: {path} (is_dir={path.is_dir()})')
elif path.stat().st_size == 0:
print(f'File is empty: {path}')
else:
# Safe to read
import csv
with open(path, 'r', encoding='utf-8', newline='') as f:
reader = csv.DictReader(f)
rows = list(reader)
print(f'Read {len(rows)} rows')os.access() — 権限の確認
os.access(path, mode)は、現在のプロセスがファイルに対して指定された権限を持っているかどうかを確認します。読み取りにはos.R_OK、書き込みにはos.W_OK、実行にはos.X_OKを使用します。特定の権限が必要な操作を実行する前に役立ちます。
import os
from pathlib import Path
def check_file_access(path):
p = Path(path)
checks = {
'exists': p.exists(),
'is_file': p.is_file(),
'readable': os.access(p, os.R_OK),
'writable': os.access(p, os.W_OK),
}
for check, result in checks.items():
status = 'OK' if result else 'FAIL'
print(f' {check}: {status}')
return all(checks.values())
if check_file_access('data/input.json'):
print('File is accessible')
else:
print('Access problem — check path and permissions')FileNotFoundErrorの捕捉
存在しないファイルを開こうとすると、OSErrorのサブクラスであるFileNotFoundErrorが発生します。これを明示的に捕捉し、想定したパスを含む分かりやすいエラーメッセージを表示してください。そうすれば、ユーザーや運用担当者は何が見つからないのかを正確に把握できます。
import json
from pathlib import Path
def load_agent_config(config_path='agent_config.json'):
path = Path(config_path)
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
print(f'Config file not found: {path.resolve()}')
print('Create agent_config.json with your settings')
print('Example: {"model": "gpt-4o", "max_retries": 3}')
return {} # return empty config as default
except json.JSONDecodeError as e:
print(f'Invalid JSON in {path}: {e}')
return {}
# --- demo ---
config = load_agent_config('does_not_exist_config.json')
print(f'Config used: {config}')
PermissionErrorの捕捉
プロセスにファイルを読み書きする権限がない場合、PermissionErrorが発生します。システムファイル、別のユーザーが所有するファイル、または制限の厳しい権限が設定されたファイルで発生することがあります。FileNotFoundErrorとは必ず分けて捕捉してください。これらには異なる対応が必要です。
from pathlib import Path
def read_file_safely(path):
try:
return Path(path).read_text(encoding='utf-8')
except FileNotFoundError:
print(f'File not found: {path}')
return None
except PermissionError:
import os
print(f'Permission denied: {path}')
print(f'File permissions: {oct(Path(path).stat().st_mode)}')
print(f'Current user: {os.getlogin()}')
print('Try: chmod +r ' + str(path))
return None
except IsADirectoryError:
print(f'Path is a directory, not a file: {path}')
return None
# --- demo ---
import os
print(read_file_safely('does_not_exist.txt'))
os.makedirs('a_directory', exist_ok=True)
print(read_file_safely('a_directory'))
IsADirectoryErrorの捕捉
ディレクトリをファイルとして開こうとすると、IsADirectoryErrorが発生します。エージェントがパスを誤って組み立てた場合に発生することがあります。たとえば、すでにディレクトリとして存在する名前をファイル名として追加した場合です。意味のあるエラー出力を提供できるよう、必ず捕捉してください。
from pathlib import Path
def safe_write(output_path, content):
path = Path(output_path)
# Check the path is not an existing directory
if path.is_dir():
raise IsADirectoryError(
f'Cannot write file: {path} is a directory. '
f'Use a filename like {path}/output.txt instead.'
)
# Ensure parent directory exists
path.parent.mkdir(parents=True, exist_ok=True)
try:
path.write_text(content, encoding='utf-8')
print(f'Written: {path} ({len(content)} chars)')
except IsADirectoryError as e:
print(f'Path error: {e}')
except PermissionError:
print(f'Cannot write to {path} — permission denied')
# --- demo ---
import os
safe_write('demo_output/report.txt', 'Agent finished the task.')
os.makedirs('already_a_dir', exist_ok=True)
try:
safe_write('already_a_dir', 'this will fail')
except IsADirectoryError as e:
print(f'Rejected: {e}')
tempfileによるアトミック書き込み
ファイルに直接書き込むのは危険です。エージェントが書き込みの途中でクラッシュすると、ファイルが不完全な状態で残り、破損する可能性があります。解決策はアトミック書き込みです。まず一時ファイルに書き込み、その後で最終的なパスに名前を変更します。POSIXシステムでは名前の変更がアトミックに行われるため、最終ファイルは必ず古いバージョンか新しいバージョンのどちらかになり、不完全な状態にはなりません。
import tempfile
import os
import json
from pathlib import Path
def atomic_write_json(file_path, data):
path = Path(file_path)
path.parent.mkdir(parents=True, exist_ok=True)
# Write to temp file in same directory
tmp_fd, tmp_path = tempfile.mkstemp(
dir=path.parent,
prefix='.tmp_',
suffix='.json'
)
try:
with os.fdopen(tmp_fd, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Atomic rename: replaces final file in one operation
os.replace(tmp_path, path)
print(f'Atomically wrote: {path}')
except Exception as e:
os.unlink(tmp_path) # clean up temp file on error
raise
# --- demo ---
atomic_write_json('demo_state/state.json', {'step': 3, 'status': 'running'})
print('File contents:', Path('demo_state/state.json').read_text(encoding='utf-8'))
並列エージェントのファイルロック
複数のエージェントインスタンスが並列に実行され、同じファイルに書き込むと、競合状態によってデータが破損します。fcntlモジュール(Linux/macOS)またはクロスプラットフォームのfilelockライブラリでファイルロックを使用し、同時書き込みを防いでください。
from filelock import FileLock, Timeout
import json
from pathlib import Path
COUNTER_FILE = Path('shared_counter.json')
LOCK_FILE = Path('shared_counter.json.lock')
def increment_counter():
lock = FileLock(str(LOCK_FILE), timeout=10)
try:
with lock:
# Only one process can be here at a time
if COUNTER_FILE.exists():
data = json.loads(COUNTER_FILE.read_text())
else:
data = {'count': 0}
data['count'] += 1
COUNTER_FILE.write_text(
json.dumps(data, indent=2)
)
return data['count']
except Timeout:
print('Could not acquire lock within 10 seconds')
return None存在確認による安全な削除
存在しないファイルを削除するとFileNotFoundErrorが発生します。Path.unlink()でディレクトリを削除しようとするとIsADirectoryErrorが発生します。安全に削除するには、Path.unlink(missing_ok=True)(Python 3.8以降)を使用するか、先に存在を確認してください。
from pathlib import Path
import shutil
# Safe file deletion (Python 3.8+)
Path('temp_output.json').unlink(missing_ok=True)
# Safe directory deletion
def safe_remove(path):
p = Path(path)
if not p.exists():
print(f'Already gone: {p}')
return
if p.is_file():
p.unlink()
print(f'Deleted file: {p}')
elif p.is_dir():
shutil.rmtree(p)
print(f'Deleted directory: {p}')
else:
print(f'Unknown file type: {p}')
# Clean up temporary workspace
safe_remove('/tmp/agent_workspace/run_001')書き込み前のディスク容量確認
大きなファイル(AIの出力、データセット、ログなど)を書き込むエージェントは、最初に利用可能なディスク容量を確認してください。shutil.disk_usage(path)は、総容量、使用容量、空き容量をバイト単位で返します。時間のかかる書き込み処理を開始する前に、空き容量が想定される出力サイズを上回っていることを確認してください。
import shutil
from pathlib import Path
def check_disk_space(output_dir, required_bytes):
path = Path(output_dir)
path.mkdir(parents=True, exist_ok=True)
usage = shutil.disk_usage(path)
free_gb = usage.free / (1024 ** 3)
required_gb = required_bytes / (1024 ** 3)
print(f'Disk free: {free_gb:.2f} GB')
print(f'Required: {required_gb:.2f} GB')
if usage.free < required_bytes * 1.1: # 10% safety margin
raise IOError(
f'Insufficient disk space: '
f'{free_gb:.2f} GB free, '
f'{required_gb:.2f} GB required'
)
return True
# Before writing a 500 MB dataset
check_disk_space('/tmp/output', 500 * 1024 * 1024)上書き前のバックアップ
エージェントが既存のファイルを更新するときは、以前のバージョンをバックアップしておくと安心です。新しい内容に問題があった場合にロールバックできます。タイムスタンプ付きのバックアップファイル名を使用し、ディスク容量を使い果たさないようバックアップ数を制限してください。
import shutil
import datetime
from pathlib import Path
def write_with_backup(file_path, content, max_backups=5):
path = Path(file_path)
backup_dir = path.parent / '.backups'
backup_dir.mkdir(exist_ok=True)
# Backup existing file
if path.exists():
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
backup = backup_dir / f'{path.name}.{timestamp}'
shutil.copy2(path, backup)
print(f'Backed up to: {backup}')
# Write new content
path.write_text(content, encoding='utf-8')
# Prune old backups
backups = sorted(backup_dir.glob(f'{path.name}.*'))
for old in backups[:-max_backups]:
old.unlink()
print(f'Pruned old backup: {old.name}')
# --- demo ---
write_with_backup('demo_notes.txt', 'version 1')
write_with_backup('demo_notes.txt', 'version 2')
print('Backups:', [p.name for p in sorted(Path('.backups').glob('demo_notes.txt.*'))])
クイックチェック:アトミック書き込み
安全なファイル書き込みパターンについての理解度を確認しましょう。
安全なファイル操作のまとめ
これで、エージェントはファイルを慎重に扱えるようになりました。
- アクセス前に確認する:
Path.exists()+Path.is_file()+os.access(path, os.R_OK) - FileNotFoundError、PermissionError、IsADirectoryErrorを分かりやすいメッセージとともに捕捉する
- クラッシュ時の不完全なファイルや破損したファイルを防ぐため、アトミック書き込み(tempfile + os.replace)を使用する
- 複数のエージェントが同じファイルに書き込む場合は、ファイルロック(filelockライブラリ)を使用する
- missing_ok=Trueを使用し、FileNotFoundErrorを発生させずに安全に削除する
- 大きな書き込みの前にディスク容量を確認し、重要なファイルを上書きする前にバックアップを作成する
よくある質問
「エラーハンドリングを伴う安全なファイル操作」レッスンは無料ですか?
はい。「エラーハンドリングを伴う安全なファイル操作」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Agentsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Agentsコースには全4レッスンが含まれています。
「エラーハンドリングを伴う安全なファイル操作」で何を学びますか?
ファイルの存在と権限を確認し、IOエラーを適切に処理します。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Agentsを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Agentsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「エラーハンドリングを伴う安全なファイル操作」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Agentsレッスンでコードを書いて実行できますか?
はい。すべてのAI Agentsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- エージェントコンテキストでのファイルの読み書き
- ディレクトリトラバーサルとファイル探索
- ファイル形式の処理:CSV、JSON、TXT
- エラーハンドリングを伴う安全なファイル操作