오류 처리를 적용한 안전한 파일 작업
파일 존재 여부와 권한을 확인하고 IO 오류를 우아하게 처리합니다.
오류 처리를 적용한 안전한 파일 작업은(는) CoddyKit의 무료 AI Agents 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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 처리
존재하지 않는 파일을 열려고 하면 FileNotFoundError(OSError의 하위 클래스)가 발생합니다. 이를 명시적으로 처리하고 예상 경로를 포함한 유용한 오류 메시지를 제공하면 사용자나 운영자가 정확히 무엇이 누락되었는지 알 수 있습니다.
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를 명확한 메시지와 함께 처리
- 충돌 시 일부만 기록되거나 손상된 파일이 생기지 않도록 원자적 쓰기(임시 파일 + os.replace) 사용
- 여러 에이전트가 같은 파일에 쓸 때 파일 잠금(filelock 라이브러리) 사용
- FileNotFoundError 없이 안전하게 삭제하려면 missing_ok=True 사용
- 대규모 쓰기 전에 디스크 공간을 확인하고, 중요한 파일을 덮어쓰기 전에 백업 보관
자주 묻는 질문
“오류 처리를 적용한 안전한 파일 작업” 강의는 무료인가요?
네 — “오류 처리를 적용한 안전한 파일 작업” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Agents 강의 전체를 잠금 해제할 수 있습니다. AI Agents 강의에는 총 4개의 강의가 포함되어 있습니다.
“오류 처리를 적용한 안전한 파일 작업”에서 뭘 배우나요?
파일 존재 여부와 권한을 확인하고 IO 오류를 우아하게 처리합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Agents을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
AI Agents을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 AI Agents은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“오류 처리를 적용한 안전한 파일 작업” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 AI Agents 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 AI Agents 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 에이전트 환경에서 파일 읽기 및 쓰기
- 디렉터리 탐색 및 파일 찾기
- 파일 형식 처리: CSV, JSON, TXT
- 오류 처리를 적용한 안전한 파일 작업