소개
본 실습에서는 최신 pathlib 모듈을 사용하여 Python 에서 파일 및 디렉터리 경로를 처리하는 방법을 배웁니다. 이전의 os.path 모듈도 여전히 흔히 사용되지만, pathlib는 경로 조작을 더욱 직관적이고 읽기 쉽게 만드는 객체 지향적인 접근 방식을 제공합니다. 여러분은 Windows, macOS, Linux 와 같은 다양한 운영 체제에서 작동하는 방식으로 경로, 파일 및 디렉터리를 생성, 검사, 검증 및 조작하는 방법을 배우게 될 것입니다.
본 실습이 끝날 때쯤에는 다음 사항에 능숙해질 것입니다.
- 경로 구성 요소를 올바르게 결합하는 방법.
- 디렉터리를 생성하고 관리하는 방법.
- 절대 경로와 상대 경로를 구별하는 방법.
- 경로의 존재 여부와 유형을 검증하는 방법.
- 복사, 이동, 삭제와 같은 일반적인 파일 작업을 수행하는 방법.
- 디렉터리 트리를 탐색하여 파일과 폴더를 찾는 방법.
기본 경로 작업
이 단계에서는 pathlib 모듈을 사용하여 파일 시스템 경로 조작의 기본 사항을 배웁니다. 여기에는 경로 구성 요소를 결합하고, 현재 작업 디렉터리를 가져오고, 새 디렉터리를 만드는 작업이 포함됩니다.
pathlib 모듈은 파일 시스템 경로를 처리하는 우아하고 플랫폼 독립적인 방법을 제공합니다. Windows 에서는 경로가 백슬래시 (\) 를 사용하는 반면, Unix 계열 시스템 (Linux, macOS) 에서는 포워드 슬래시 (/) 를 사용합니다. pathlib를 사용하면 수동 조정 없이도 코드가 모든 플랫폼에서 작동하도록 보장할 수 있습니다.
참고: WebIDE(VS Code 와 유사) 사용에 익숙하지 않다면, 이 실습에서 먼저 학습하십시오.
먼저, 경로 구성 요소를 결합하는 방법을 살펴보겠습니다. / 연산자는 joinpath()에 대한 직관적인 단축키입니다.
- 왼쪽 파일 탐색기에서
main.py파일을 엽니다. main.py의 내용을 다음 코드로 바꿔 경로 구성 요소를 결합하고 결과를 출력합니다.
from pathlib import Path
## / 연산자를 사용하여 경로 구성 요소 결합
my_path = Path('usr') / 'bin' / 'spam'
print(my_path)
- 터미널에서 스크립트를 실행합니다.
python3 main.py
pathlib가 Unix 스타일 경로를 올바르게 구성하는 것을 보여주는 다음 출력을 보게 될 것입니다.
usr/bin/spam
다음으로, Path.cwd()를 사용하여 현재 작업 디렉터리를 가져와 보겠습니다.
main.py의 내용을 다음 코드로 바꿉니다 (이전 코드를 대체합니다).
from pathlib import Path
## 현재 작업 디렉터리 가져오기
cwd = Path.cwd()
print(cwd)
- 스크립트를 다시 실행합니다.
python3 main.py
출력에는 현재 작업 디렉터리가 표시되며, 이 환경에서는 /home/labex/project입니다.
/home/labex/project
마지막으로, 새 디렉터리를 만들어 보겠습니다. 부모 디렉터리가 존재하지 않는 곳에 디렉터리를 생성하려고 하면 FileNotFoundError가 발생합니다. 이를 방지하려면 mkdir(parents=True)를 사용하면 필요한 모든 부모 디렉터리가 생성됩니다.
- 중첩된 디렉터리 구조를 생성하기 위해
main.py의 내용을 다음 코드로 바꿉니다.
from pathlib import Path
## 현재 작업 디렉터리 가져오기
cwd = Path.cwd()
## 새 중첩 디렉터리에 대한 경로 정의
new_dirs = cwd / 'delicious' / 'walnut' / 'waffles'
## 누락된 부모 디렉터리를 포함하여 디렉터리 생성
new_dirs.mkdir(parents=True, exist_ok=True)
print(f"Successfully created: {new_dirs}")
exist_ok=True인수는 디렉터리가 이미 존재하는 경우 오류가 발생하는 것을 방지합니다. 따라서 스크립트를 여러 번 실행해도 오류가 발생하지 않습니다.
- 스크립트를 실행합니다.
python3 main.py
스크립트는 중첩된 디렉터리를 생성하고 확인 메시지를 출력합니다.
Successfully created: /home/labex/project/delicious/walnut/waffles
터미널에서 ls -R delicious를 실행하여 디렉터리 생성을 확인할 수 있습니다.
절대 경로 대 상대 경로
이 단계에서는 절대 경로와 상대 경로를 구별하고 작업하는 방법을 배웁니다.
- **절대 경로 (absolute path)**는 파일 시스템의 루트 (예: Linux 의
/또는 Windows 의C:\) 에서 시작합니다. - **상대 경로 (relative path)**는 현재 작업 디렉터리를 기준으로 정의됩니다.
pathlib는 경로가 절대 경로인지 확인하고, 경로를 절대 형태로 변환하며, 시작 지점으로부터의 상대 경로를 찾는 메서드를 제공합니다.
먼저, is_absolute()를 사용하여 경로가 절대 경로인지 확인해 보겠습니다.
main.py의 내용을 다음 코드로 바꿉니다.
from pathlib import Path
## 경로가 절대 경로인지 확인
print(f"Path('/') is absolute: {Path('/').is_absolute()}")
print(f"Path('..') is absolute: {Path('..').is_absolute()}")
- 스크립트를 실행합니다.
python3 main.py
출력은 /가 절대 경로인 반면 ..(부모 디렉터리) 는 상대 경로임을 확인시켜 줍니다.
Path('/') is absolute: True
Path('..') is absolute: False
다음으로, resolve()를 사용하여 상대 경로의 전체 절대 경로를 얻을 수 있습니다. 이는 .(현재 디렉터리) 또는 ..(부모 디렉터리) 와 같은 상대 경로를 완전하고 모호하지 않은 형태로 변환할 때 유용합니다.
main.py의 내용을 다음 코드로 바꿉니다.
from pathlib import Path
## 상대 경로를 절대 형태로 변환
## '..'는 현재 작업 디렉터리의 부모 디렉터리를 참조합니다
resolved_path = Path('..').resolve()
print(f"Current working directory: {Path.cwd()}")
print(f"Resolved path of '..': {resolved_path}")
- 스크립트를 실행합니다.
python3 main.py
출력에는 현재 디렉터리와 해당 부모의 변환된 절대 경로가 표시됩니다.
Current working directory: /home/labex/project
Resolved path of '..': /home/labex
마지막으로, relative_to()를 사용하여 한 경로에서 다른 경로까지의 상대 경로를 계산할 수 있습니다.
- 상대 경로를 계산하기 위해
main.py의 내용을 다음 코드로 바꿉니다.
from pathlib import Path
## 시작 지점으로부터의 상대 경로 가져오기
## 이는 루트 디렉터리 '/'에 상대적인 '/etc/passwd'로의 경로를 보여줍니다
relative_path = Path('/etc/passwd').relative_to('/')
print(f"Path to '/etc/passwd' relative to '/': {relative_path}")
- 스크립트를 실행합니다.
python3 main.py
출력은 루트 디렉터리에 상대적인 경로를 보여줍니다.
Path to '/etc/passwd' relative to '/': etc/passwd
경로 유효성 검사 및 검사
이 단계에서는 경로가 존재하는지, 파일인지 디렉터리인지와 같은 경로의 속성을 확인하는 방법을 배웁니다. 또한 파일 크기와 같은 파일 메타데이터를 검색하는 방법도 배웁니다.
일반적인 작업은 파일이나 디렉터리에 액세스하기 전에 해당 항목이 존재하는지 확인하는 것입니다. exists() 메서드가 이 작업에 완벽합니다.
main.py를 열고 내용을 다음 코드로 바꿉니다. 이 코드는 현재 디렉터리 (.), 파일 (setup.py), 그리고 존재하지 않는 파일의 존재 여부를 확인합니다.
from pathlib import Path
## 경로 존재 여부 확인
print(f"Path('.') exists: {Path('.').exists()}")
print(f"Path('setup.py') exists: {Path('setup.py').exists()}")
print(f"Path('nonexistentfile') exists: {Path('nonexistentfile').exists()}")
- 스크립트를 실행합니다.
python3 main.py
출력은 어떤 경로가 존재하는지 확인해 줍니다.
Path('.') exists: True
Path('setup.py') exists: True
Path('nonexistentfile') exists: False
더 구체적으로 is_file()을 사용하여 경로가 파일인지, 또는 is_dir()을 사용하여 디렉터리인지를 확인할 수도 있습니다.
- 이러한 확인을 수행하기 위해
main.py의 내용을 다음 코드로 바꿉니다.
from pathlib import Path
## 경로가 파일인지 확인
print(f"Path('setup.py').is_file(): {Path('setup.py').is_file()}")
print(f"Path('/home').is_file(): {Path('/home').is_file()}")
## 경로가 디렉터리인지 확인
print(f"Path('/').is_dir(): {Path('/').is_dir()}")
print(f"Path('setup.py').is_dir(): {Path('setup.py').is_dir()}")
- 스크립트를 실행합니다.
python3 main.py
출력은 각 경로의 유형을 올바르게 식별합니다.
Path('setup.py').is_file(): True
Path('/home').is_file(): False
Path('/').is_dir(): True
Path('setup.py').is_dir(): False
마지막으로, 파일 크기와 같은 자세한 파일 정보를 얻으려면 stat() 메서드를 사용할 수 있습니다. 이 메서드는 파일 크기 (바이트 단위) 를 포함하여 다양한 속성을 가진 객체를 반환합니다.
- 시스템 파일의 크기를 얻기 위해
main.py의 내용을 다음 코드로 바꿉니다.
from pathlib import Path
import os
## 파일 경로 정의
file_path = Path('/bin/python3')
## 파일 통계 정보 가져오기
stat_info = file_path.stat()
## 바이트 단위의 크기 출력
print(f"File: {file_path}")
print(f"Size: {stat_info.st_size} bytes")
- 스크립트를 실행합니다.
python3 main.py
출력은 Python 실행 파일의 크기를 바이트 단위로 표시합니다. 정확한 크기는 다를 수 있습니다.
File: /bin/python3
Size: 10024 bytes
파일 및 폴더 관리
이 단계에서는 pathlib과 함께 shutil 모듈을 사용하여 파일 복사, 이동, 이름 변경, 삭제와 같은 일반적인 파일 시스템 작업을 수행하는 방법을 배웁니다.
shutil 모듈은 파일 작업을 위한 고수준 (high-level) 함수를 제공합니다. 파일 복사부터 시작하겠습니다. shutil.copy()는 단일 파일을 새 위치로 복사합니다.
- 왼쪽 파일 탐색기에서
manage_files.py파일을 엽니다. manage_files.py의 내용을 다음 코드로 대체합니다. 이 코드는spam.txt를/tmp/delicious디렉터리로 복사합니다.
import shutil
from pathlib import Path
## 소스 및 대상 정의
source_file = Path('/tmp/spam.txt')
destination_dir = Path('/tmp/delicious')
## 파일 복사
shutil.copy(source_file, destination_dir)
print(f"'{source_file}'를 '{destination_dir}'로 복사했습니다.")
## 복사하면서 파일 이름을 바꿀 수도 있습니다.
shutil.copy('/tmp/eggs.txt', '/tmp/delicious/eggs2.txt')
print("'eggs.txt'를 'eggs2.txt'로 복사 및 이름 변경했습니다.")
- 스크립트를 실행합니다:
python3 manage_files.py
전체 디렉터리 트리를 복사하려면 shutil.copytree()를 사용합니다.
manage_files.py끝에 다음 줄을 추가합니다 (기존 코드 뒤에 추가):
## 전체 디렉터리 트리 복사
shutil.copytree('/tmp/bacon', '/tmp/bacon_backup')
print("디렉터리 '/tmp/bacon'를 '/tmp/bacon_backup'로 복사했습니다.")
참고: 이 스크립트를 여러 번 실행하면 대상 디렉터리가 이미 존재하기 때문에
shutil.copytree()가 실패합니다. 이는 예상된 동작입니다. 일단 한 번만 실행하십시오.
- 스크립트를 다시 실행하여 작동하는지 확인합니다.
다음으로 shutil.move()를 사용하여 파일 이동 및 이름 변경을 해보겠습니다. 이 함수는 파일을 다른 디렉터리로 이동하거나 이름을 변경할 수 있습니다.
bacon.txt를 이동하도록manage_files.py의 전체 내용을 다음 코드로 대체합니다:
import shutil
from pathlib import Path
## 대상 디렉터리가 없으면 생성
Path('/tmp/eggs').mkdir(parents=True, exist_ok=True)
## 파일을 디렉터리로 이동 (소스가 존재하는 경우에만)
bacon_source = Path('/tmp/bacon.txt')
if bacon_source.exists():
shutil.move('/tmp/bacon.txt', '/tmp/eggs')
print("'/tmp/bacon.txt'를 '/tmp/eggs'로 이동했습니다.")
else:
print("'/tmp/bacon.txt'를 찾을 수 없습니다 (이미 이동되었을 수 있음)")
## 파일 이동 및 이름 변경
## 먼저, 이동할 파일을 다시 생성합니다.
Path('/tmp/bacon.txt').touch()
shutil.move('/tmp/bacon.txt', '/tmp/eggs/new_bacon.txt')
print("'/tmp/bacon.txt'를 '/tmp/eggs/new_bacon.txt'로 이동 및 이름 변경했습니다.")
참고: 코드는 이동하기 전에 소스 파일이 존재하는지 확인하므로 이 스크립트를 여러 번 안전하게 실행할 수 있습니다. 두 번째 부분은 항상 새 파일을 생성하고 이동합니다.
- 스크립트를 실행합니다.
마지막으로 파일 및 폴더 삭제에 대해 살펴보겠습니다.
Path.unlink()는 단일 파일을 삭제합니다.Path.rmdir()는 빈 디렉터리를 삭제합니다.shutil.rmtree()는 디렉터리와 그 안의 모든 내용을 삭제합니다.
manage_files.py의 전체 내용을 다음으로 대체합니다:
from pathlib import Path
import shutil
## 삭제할 파일 생성
file_to_delete = Path('/tmp/deleteme.txt')
file_to_delete.touch()
print(f"'{file_to_delete}' 생성됨")
## 파일 삭제
file_to_delete.unlink()
print(f"'{file_to_delete}' 삭제됨")
## 삭제할 디렉터리 생성
dir_to_delete = Path('/tmp/temp_dir')
dir_to_delete.mkdir(exist_ok=True)
## rmdir() 은 빈 디렉터리에서만 작동합니다.
dir_to_delete.rmdir()
print(f"빈 디렉터리 '{dir_to_delete}' 삭제됨")
## shutil.rmtree() 를 사용하여 디렉터리와 그 내용을 삭제합니다.
## 참고: 디렉터리가 존재하지 않으면 실패하며, 이 예제에서는 괜찮습니다.
if Path('/tmp/bacon_backup').exists():
shutil.rmtree('/tmp/bacon_backup')
print("디렉터리 '/tmp/bacon_backup'와 그 안의 모든 내용 삭제됨")
else:
print("디렉터리 '/tmp/bacon_backup'가 존재하지 않습니다 (이미 삭제되었을 수 있음)")
참고: 이 스크립트를 여러 번 실행하면 파일이나 디렉터리가 이미 삭제되었기 때문에 일부 작업이 실패할 수 있습니다. 이는 정상이며 이러한 작업이 멱등성 (idempotent, 안전하게 반복할 수 없음) 이 아님을 보여줍니다.
- 스크립트를 실행하여 삭제 작업을 수행합니다.
디렉토리 순회
이 단계에서는 디렉토리의 내용을 나열하고 디렉토리 트리를 재귀적으로 순회하는 방법을 배웁니다.
디렉토리의 내용을 나열하려면 iterdir() 메서드를 사용할 수 있으며, 이 메서드는 해당 경로 내의 모든 항목 (파일 및 디렉토리) 에 대한 이터레이터 (iterator) 를 반환합니다.
- 먼저, 샘플 디렉토리 구조를 설정해 보겠습니다. 터미널에서 다음 명령을 실행합니다.
mkdir -p ~/project/data/level1 && touch ~/project/data/file0.txt ~/project/data/level1/file1.txt
- 이제
main.py를 열고/usr/bin디렉토리의 내용을 나열하도록 내용을 다음 코드로 대체합니다.
from pathlib import Path
## 디렉토리 내용 나열
print("Contents of '/usr/bin':")
count = 0
for f in Path('/usr/bin').iterdir():
if count < 5: ## 출력을 짧게 유지하기 위해 처음 5 개 항목만 출력
print(f)
count += 1
else:
break
- 스크립트를 실행합니다.
python3 main.py
출력에는 /usr/bin 내의 처음 몇 개 파일과 디렉토리가 표시됩니다. 정확한 목록은 다를 수 있습니다.
Contents of '/usr/bin':
/usr/bin/[
/usr/bin/addpart
/usr/bin/apt
/usr/bin/apt-cache
/usr/bin/apt-cdrom
iterdir()와 stat()을 결합하여 디렉토리 내 모든 파일의 총 크기를 계산할 수 있습니다.
main.py의 내용을 다음 코드로 대체합니다. 이 코드는 직접적인 자식 파일의 크기만 합산하며 하위 디렉토리는 재귀적으로 탐색하지 않는다는 점에 유의하십시오.
from pathlib import Path
total_size = 0
directory = Path('/usr/bin')
for sub_path in directory.iterdir():
if sub_path.is_file(): ## 파일 크기만 더하도록 확인
total_size += sub_path.stat().st_size
print(f"Total size of files in '{directory}': {total_size} bytes")
- 스크립트를 실행합니다.
/usr/bin내에 직접 포함된 모든 파일의 총 크기가 출력됩니다.
더 복잡한 작업을 위해서는 전체 디렉토리 트리를 "탐색 (walk)"하여 모든 하위 디렉토리와 파일을 방문해야 할 수 있습니다. rglob() 메서드가 이 작업에 완벽합니다. 특정 패턴과 일치하는 모든 경로를 재귀적으로 찾습니다. 패턴으로 *를 사용하면 모든 항목이 일치합니다.
- 먼저, 이 예제를 위해 더 복잡한 디렉토리 구조를 생성합니다. 터미널에서 다음을 실행합니다.
mkdir -p /tmp/delicious/cats /tmp/delicious/walnut/waffles
touch /tmp/delicious/spam.txt /tmp/delicious/cats/catnames.txt /tmp/delicious/walnut/waffles/butter.txt
참고: 이러한 명령은 여러 번 안전하게 실행할 수 있습니다.
mkdir -p명령은 디렉토리가 이미 존재하는 경우 실패하지 않으며,touch명령은 파일이 이미 존재하는 경우 파일 타임스탬프를 업데이트합니다.
main.py의 내용을/tmp/delicious디렉토리를 탐색하도록 다음 코드로 대체합니다.
from pathlib import Path
p = Path('/tmp/delicious')
print(f"Walking directory tree for: {p}")
for i in p.rglob('*'):
print(i)
- 스크립트를 실행합니다.
python3 main.py
출력에는 /tmp/delicious 및 그 하위 디렉토리 내의 모든 파일과 폴더가 나열되어 rglob()이 전체 트리를 순회하는 방식을 보여줍니다.
Walking directory tree for: /tmp/delicious
/tmp/delicious/spam.txt
/tmp/delicious/eggs2.txt
/tmp/delicious/cats
/tmp/delicious/walnut
/tmp/delicious/cats/catnames.txt
/tmp/delicious/walnut/waffles
/tmp/delicious/walnut/waffles/butter.txt
요약
실습을 완료하신 것을 축하드립니다! 여러분은 현대적인 pathlib 모듈을 사용하여 Python 에서 파일 및 디렉터리 경로를 효과적으로 관리하는 방법을 배웠습니다.
이 실습 전반에 걸쳐 다음 사항들을 연습했습니다.
Path객체와/연산자를 사용하여 플랫폼 독립적인 방식으로 경로를 생성하고 결합하는 방법.- 현재 작업 디렉터리 (current working directory) 를 가져오고
mkdir(parents=True)를 사용하여 중첩된 디렉터리를 생성하는 방법. - 절대 경로 (absolute paths) 와 상대 경로 (relative paths) 를 구별하고
is_absolute(),resolve(),relative_to()를 사용하여 경로 간 변환하는 방법. exists(),is_file(),is_dir()로 경로를 검증하고stat()으로 파일 메타데이터를 검사하는 방법.shutil모듈과pathlib메서드를 사용하여 복사, 이동, 삭제와 같은 필수적인 파일 관리 작업을 수행하는 방법.iterdir()로 내용을 나열하고rglob()으로 항목을 재귀적으로 찾는 방법으로 디렉터리 구조를 순회하는 방법.
이러한 기술은 파일 시스템과 상호 작용하는 견고하고 유지보수 가능한 Python 애플리케이션을 작성하는 데 필수적입니다.



