파이썬 pathlib 사용법

os.path 대신 pathlib을 사용하는 더 나은 파일 경로 다루기

파이썬 pathlib 사용법
Photo by Jan Antonin Kolar / Unsplash

파이썬에서는 os.path 라는 파일 시스템 내의 경로를 다루는 모듈이 있습니다. 예전에는 파일이나 디렉토리의 경로는 단순히 문자열로만 표현되었습니다. 그런데 OS마다 경로상의 구분자가 다른 것 때문에 이런 저런 불편함이 많았습니다. 그리고 문자열로 경로를 표현하면 경로와 관련된 연산이나 조작 기능이 문자열에 의해 제공되지 않으므로 별도의 함수 형태로 API가 설계되어야 하는 제약도 있었습니다.

pathlib은 파이썬 3.4부터 (그러니까 이미 나온지 10년이 훌쩍 넘었다는 이야기이기도 합니다.) 도입된 기능으로, 파일시스템의 경로를 객체지향적인 방식으로 다룰 수 있게 하면서 OS마다 다른 경로 구분자를 사용하지 않고 슬래시로만 사용할 수 있도록 하는 등 몇 가지 개선 사항을 포함하고 있습니다.

경로 만들기

Path 객체를 생성할 때 타깃이 되는 위치를 절대경로나 상대경로를 표현하는 문자열을 사용해서 지정할 수 있습니다. 지정하지 않는다면 현재 경로를 표현하는 Path 객체가 생성됩니다.

💡
'현재 경로'란 스크립트나 파이썬 인터프리터를 실행하는 명령을 입력한 쉘 상의 경로입니다. 혹은 윈도 시작메뉴나 프로그램 바로가기에서는 "시작 위치"라는 속성이 있는데, 이 경로가 현재 경로로 사용됩니다.
from pathlib import Path

# "현재경로"를 Path 객체로 생성하기
p1 = Path()

# 절대경로를 사용해서 생성하기
p2 = Path('D:/docs/python/samples')

# 상대경로를 사용하기
p3 = Path('my_text_files/t001.txt')

경로 합치기

os.path 모듈에서는 os.path.join 이라는 함수를 사용해서 둘 이상의 경로를 합쳤습니다. 이 때 사용되는 경로 구분자는 os마다 다른데, 이 값은 os.path.sep 이라는 속성을 참조합니다.

import os.path

a = 'C:/Users/myname'
b = 'Documents/temp'
c = os.path.join(a, b)
# c = 'C:/Users/myname\\Documents/temp'

os.path.join 함수는 단순히 os.path.sep 문자를 구분자로 하여 전달된 인자들을 연결하는 기능만 수행하기 때문에, 윈도의 경우 결과로 만들어지는 경로에 슬래시와 역슬래시가 혼합될 수 있는 문제를 가지고 있습니다. (물론 파이썬에서는 역슬래시를 사용하는 불편함을 해소하기 위해서 슬래시나 역슬래시를 모두 올바른 경로 구분자로 치환하여 작동하도록 합니다...)

Path를 사용해서 두 경로를 연결할 때에는 단순히 / 연산자를 사용하면 됩니다. 이 때 두 경로 중 하나만 Path 객체이면 됩니다.

p = Path('my_directory')
p2 = 'C:/Users\\myaccount' / p / 'myfile.txt'  # 경로에 슬래시와 역슬래시가 혼재해도 됩니다. 

print(p2())
# C:\Users\myaccount\my_directory\myfile.txt

파일열기

Path 객체는 이른바, file-like한 객체로 파일 이름이나 경로 문자열을 인자로 받는 open(), sqlite3.open(), shutil.copy(), subprocess.run()등의 파이썬 표준 라이브러리의 함수에서는 파일 경로 문자열을 대체해서 전달할 수 있습니다.

그 외에도 Path 객체는 가리키는 대상이 파일인 경우, 파일을 열 수 있는 몇 가지 방식을 제공합니다.

  • p.open(mode=, encoding=..) : open() 함수와 같이 path 객체가 가리키는 경로의 파일을 지정한 모드로 열고 파일 객체를 반환합니다. 이 함수는 컨텍스트 매니저 프로토콜, 즉 with 구문도 지원합니다.
  • p.read_bytes() : 가리키는 대상이 존재하는 파일인 경우, 해당 파일을 열어서 내용을 바이트 배열로 읽어 들입니다.
  • p.read_text(encoding=) : 가리키는 대상이 존재하는 파일인 경우, 해당 팡리을 열어서 내용을 문자열로 읽어들입니다.

파일 이름 변경하기

Path 객체는 rename 메소드를 사용하여 파일의 이름변경 및 경로변경(이동)을 지원합니다. rename() 이라는 이름과 달리, 새로운 경로를 지정하여 이동시키는 것임에 유의해야 합니다. 경로를 유지하고 이름만 변경하려는 경우, path.with_name() 메소드를 사용하여 새로운 이름을 가진 경로를 생성하고, 이를 rename()에 적용하면 됩니다.

# Path를 사용한 이름변경

p = Path('../location/somefile.txt')
new = p.with_name('new_file.txt')
p.rename(new)

이름이나 확장자를 변경한 새로운 Path를 생성하는 방법은 다음과 같습니다. 주로 원본을 유지하고, 새로운 파일을 만드려는 경우에 사용하기 좋습니다.

  • path.with_name('newname.ext') : 경로의 마지막인 파일명 부분만 변경된 새 경로를 생성합니다. 여기서 "이름"은 파일 이름과 확장자를 모두 포함합니다.
  • path.with_stem(new_stem) : 확장자는 그대로 유지하고 파일의 이름 부분만 변경된 경로를 생성합니다.
  • path.with_suffix() : 확장자만 변경할 때 사용합니다. suffix에는 "." 문자가 포함되어야 합니다.

패턴 매칭하기

특정한 경로를 기준으로 하위 디렉토리나 파일을 검색할 때에는 전통적으로 glob.glob()나 os.walk()를 사용할 수 있습니다. Path 객체도 glob()라는 메소드를 제공하여 주어진 패턴에 매칭하는 파일이나 디렉토리를 검색할 수 있습니다. 특히 **/*.txt 와 같은 패턴을 사용하면 추가적인 플래그 지정 없이 간단하게 재귀적인 하위 디렉토리 탐색을 할 수 있습니다.

# 특정 디렉토리 하위의 모든 zip 파일 찾기 (재귀적)

p = Path('some_folder')
for file in p.glob('./**/*.zip'):
  do_something_with_zip(file)
  

경로의 정보 확인하기

그 외에 경로 조작과 관련한 몇 가지 기능들이 더 있습니다. 보다 자세한 사항은 파이썬 문서를 통해 확인해보는 것을 추천합니다.

  • path.exists() : 해당 경로에 실제 파일/디렉토리가 있는지 확인
  • path.is_file() / path.is_dir() : 경로가 파일인지 디렉토리인지 확인
  • path.parent, list(path.parents) : 부모 경로 얻기
  • path.parts : 경로를 각 부분으로 쪼개기