파이썬128번5 MIN
pathlib로 파일 읽고 쓰기: 현대적인 파일 I/O
Python 3.4+의 pathlib.Path를 활용한 파일 읽기·쓰기를 설명합니다. read_text·write_text·read_bytes·write_bytes, / 연산자 경로 조합, glob·rglob, mkdir·unlink·rename, os.path 대비 장점까지 다룹니다.
이 글은 PALDYN Tech Blog의 글입니다.원문 보기
지난 글에서 seek()와 tell()로 파일 포인터를 제어하는 방법을 살펴봤다. 지금까지 open()에 문자열 경로를 전달하는 방식을 써왔다면, Python 3.4부터 도입된 pathlib.Path를 사용하면 경로 조작과 파일 I/O를 더 직관적으로 처리할 수 있다. 오늘날 파이써닉한 코드에서 os.path보다 pathlib를 선호하는 이유를 알아보자.
pathlib.Path 기본 개념
from pathlib import Path
# 경로 생성
p = Path("data/logs/app.log") # 상대 경로
p = Path("/home/user/data/app.log") # 절대 경로
p = Path.home() / "Documents" / "note.txt" # 홈 디렉터리
# / 연산자로 경로 이어붙이기
base = Path("project")
config = base / "config" / "settings.json"
print(config) # project/config/settings.json
# 현재 디렉터리
cwd = Path.cwd()
파일 읽기: read_text · read_bytes
from pathlib import Path
p = Path("data/report.txt")
# 텍스트 읽기 — with open() 없이 한 줄
content = p.read_text(encoding="utf-8")
# 이진 읽기
raw = p.read_bytes() # → bytes
# 라인 단위로 처리 (대용량)
with p.open("r", encoding="utf-8") as f:
for line in f:
process(line)
read_text()와 read_bytes()는 내부적으로 with open() 블록을 사용하므로 자동으로 파일이 닫힌다. 단, 파일 전체를 메모리에 올리므로 대용량 파일에는 p.open()으로 스트리밍이 더 적합하다.
파일 쓰기: write_text · write_bytes
from pathlib import Path
p = Path("output/result.txt")
# 부모 디렉터리가 없으면 먼저 생성
p.parent.mkdir(parents=True, exist_ok=True)
# 텍스트 쓰기 (파일 있으면 덮어씀)
bytes_written = p.write_text("결과 내용\n", encoding="utf-8")
# 이진 쓰기
p.write_bytes(b"\x89PNG\r\n\x1a\n")
# 추가 모드 — append는 open() 사용
with p.open("a", encoding="utf-8") as f:
f.write("추가 내용\n")
write_text()는 항상 파일을 새로 써서 기존 내용을 덮어쓴다. 추가(append) 모드가 필요하면 p.open("a") 또는 open(p, "a")를 써야 한다.
pathlib 핵심 메서드
경로 속성과 메타데이터
from pathlib import Path
p = Path("data/logs/app.log")
# 경로 분해
print(p.name) # 'app.log'
print(p.stem) # 'app' (확장자 제외)
print(p.suffix) # '.log'
print(p.parent) # data/logs
print(p.parts) # ('data', 'logs', 'app.log')
# 절대 경로
print(p.resolve()) # /home/user/project/data/logs/app.log
# 존재 여부 및 타입
print(p.exists()) # True/False
print(p.is_file()) # True
print(p.is_dir()) # False
# 파일 메타데이터
stat = p.stat()
print(stat.st_size) # 파일 크기 (bytes)
print(stat.st_mtime) # 마지막 수정 시간 (Unix timestamp)
import datetime
mtime = datetime.datetime.fromtimestamp(stat.st_mtime)
print(mtime.strftime("%Y-%m-%d %H:%M:%S"))
디렉터리 탐색: glob · rglob
from pathlib import Path
base = Path("project")
# 현재 디렉터리에서 .py 파일
for f in base.glob("*.py"):
print(f)
# 재귀 탐색 (모든 하위 폴더)
for f in base.rglob("*.py"):
print(f)
# 특정 패턴
for f in base.glob("test_*.py"):
print(f)
# 모든 파일 (디렉터리 제외)
py_files = [f for f in base.rglob("*") if f.is_file()]
# 파일 크기 합계
total = sum(f.stat().st_size for f in base.rglob("*") if f.is_file())
print(f"총 크기: {total:,} bytes")
파일 시스템 조작
from pathlib import Path
# 디렉터리 생성
Path("output/reports/2024").mkdir(parents=True, exist_ok=True)
# 파일 삭제
p = Path("temp.txt")
p.unlink(missing_ok=True) # 없어도 에러 없음 (Python 3.8+)
# 디렉터리 삭제 (비어있어야 함)
Path("empty_dir").rmdir()
# 이름 변경 / 이동
Path("old.txt").rename("new.txt")
# 복사 (shutil 사용)
import shutil
shutil.copy2("source.txt", "dest.txt") # 메타데이터 포함
shutil.copytree("src_dir", "dst_dir") # 디렉터리 재귀 복사
shutil.rmtree("old_dir") # 디렉터리 재귀 삭제
pathlib vs os.path 선택 기준
| 상황 | 권장 |
|---|---|
| 새 코드 작성 | pathlib |
| 기존 레거시 코드 유지 | os.path (점진적 교체) |
| 경로를 str로 받는 API | str(p) 또는 os.fspath(p) |
| 대용량 파일 스트리밍 | p.open() 또는 open(p) |
| 전체 읽기·쓰기 | p.read_text() / p.write_text() |
# pathlib Path는 str이 필요한 곳에서 자동 변환
import subprocess
p = Path("script.py")
subprocess.run(["python", p]) # Path → str 자동
subprocess.run(["python", str(p)]) # 명시적
# os.fspath()로 명시적 변환
os.fspath(p) # → '/absolute/path/script.py'
지난 글: seek·tell: 파일 포인터로 임의 위치 읽기·쓰기
다음 글: mmap: 메모리 맵 파일로 대용량 처리
읽어주셔서 감사합니다. 😊

