본문으로 건너뛰기
개발 머꼬
개발 노트Python
hohyeon.dev20

os.path.join과 문자열 자르기로 경로를 다루다가 pathlib으로 정리

  • #Engineering Note
  • #Python

문제 발생

파일 경로를 다루는 코드가 문자열 조작으로 가득했습니다. 확장자를 바꾸는 부분은 특히 취약했습니다.

import os

path = os.path.join(base, "reports", name + ".csv")
stem = os.path.splitext(os.path.basename(path))[0]
out = os.path.join(os.path.dirname(path), stem + ".json")

os.makedirs(os.path.dirname(out), exist_ok=True)
with open(path) as f:
    content = f.read()

name에 점이 들어 있으면 splitext의 결과가 달라졌고, 윈도우에서 구분자가 섞이는 문제도 있었습니다.

원인 분석

os.path는 경로를 문자열로 다룹니다. 그래서 결합, 분해, 확장자 교체가 전부 문자열 연산이고, 그 연산 각각이 실수할 자리입니다. 구분자도 직접 신경 써야 합니다.

pathlib은 경로를 객체로 다룹니다. 결합에는 / 연산자를 쓰고, 구성 요소는 속성으로 읽으며, 파일 읽기·쓰기와 디렉터리 생성 메서드를 경로 객체 자신이 갖고 있습니다.

해결 방안

  1. 결합은 / 연산자로 합니다.
from pathlib import Path

path = Path(base) / "reports" / f"{name}.csv"
  1. 구성 요소는 속성으로 읽습니다. 문자열을 자르지 않습니다.
p = Path("my/library.tar.gz")
p.name       # 'library.tar.gz'
p.stem       # 'library.tar'
p.suffix     # '.gz'
p.suffixes   # ['.tar', '.gz']
p.parent     # Path('my')
  1. 확장자 교체는 with_suffix입니다. 마지막 확장자만 바꾸므로 이름에 점이 있어도 안전합니다.
out = path.with_suffix(".json")
Path("README").with_suffix(".txt")     # README.txt (없으면 붙인다)
Path("README.txt").with_suffix("")     # README   (빈 문자열이면 제거)
  1. 디렉터리 생성과 파일 입출력을 경로에서 바로 합니다.
out.parent.mkdir(parents=True, exist_ok=True)
content = path.read_text(encoding="utf-8")
out.write_text(json.dumps(data), encoding="utf-8")
  1. 탐색은 glob/rglob을 씁니다. rglob은 재귀 탐색입니다.
for csv in Path("data").rglob("*.csv"):
    ...
  1. 존재 확인과 정규화도 메서드입니다. exists(), is_file(), is_dir()은 예외 없이 불린을 돌려주고, resolve()는 심볼릭 링크와 ..을 정리한 절대 경로를 만듭니다. 사용자 입력으로 만든 경로를 다룰 때는 resolve() 후 기준 디렉터리 안에 있는지 확인하는 것이 경로 탈출을 막는 기본 검사입니다.
  2. 문자열이 필요한 곳에는 str(path)를 씁니다. 대부분의 표준 라이브러리는 Path를 그대로 받지만, 오래된 서드파티 함수는 문자열을 요구하기도 합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.