운영체제와 상관없는 경로 설정
한마디 요약
pathlib.Path(파이썬 3.4+ 표준 라이브러리, 경로를 객체로 다루는 모듈)를 써서 “어느 OS에서 돌리든, 어느 폴더에서 돌리든” 똑같이 동작하는 파일 경로를 만드는 법임. os.path는 레거시, dotenv는 외부 설정 분리용으로 조합해 씀.
왜 경로 이식성이 중요한가
- Windows는 경로 구분자가
\, macOS/Linux는/. 문자열로 직접 합치면 한쪽에서만 동작함. - 현재 작업 디렉토리(
cwd)가 실행 위치에 따라 달라짐. 상대 경로로만 쓰면 실행 장소 바뀔 때마다 깨짐. - 팀원마다 로컬 경로가 달라, 코드에 절대 경로를 박으면 공유 불가.
일상 비유: 택배 주소를 “우리 집 옆옆집”이라고 적으면 내가 이사 가면 무효. “광역시 → 구 → 동 → 번지”처럼 기준점(최상위 루트)부터 내려가는 공식 주소로 적어야 어디서든 유효함.
pathlib
pathlib 방식 (현대식, 권장)
📂 가정하는 폴더 구조
MyProject/ <-- (여기가 루트: BASE_DIR)
│
├── main.py <-- (현재 실행 중인 코드 파일)
├── .env <-- (1. 루트에 있는 파일)
│
├── config/ <-- (폴더)
│ └── settings.yaml <-- (2. 루트 > 폴더 > 파일)
│
└── models/ <-- (폴더)
└── v1/ <-- (하위 폴더)
└── model.bin <-- (3. 루트 > 폴더 > 폴더 > 파일)
파이썬 코드 예시
from pathlib import Path
# [Step 0] 기준점 잡기 (가장 중요!)
# __file__ : 현재 실행 중인 파일(main.py)의 경로 문자열
# .resolve() : 상대 경로를 절대 경로로 변환 + 심볼릭 링크 해석
# .parent : 파일이 위치한 폴더 (여기서는 MyProject 루트)
BASE_DIR = Path(__file__).resolve().parent
print(f"프로젝트 루트 경로: {BASE_DIR}")
print("-" * 30)
# [Case 1] 루트에 바로 있는 파일 (.env)
# 설명: 루트 경로 뒤에 파일 이름을 / 연산자로 이어 붙임
root_file = BASE_DIR / ".env"
print(f"1. 루트 파일 경로:\n {root_file}")
# [Case 2] 루트 > 폴더 > 파일 (config/settings.yaml)
# 설명: 폴더명과 파일명을 슬래시(/) 연산자로 이어 붙임
folder_file = BASE_DIR / "config" / "settings.yaml"
print(f"2. 폴더 내 파일 경로:\n {folder_file}")
# [Case 3] 루트 > 폴더 > 폴더 > 파일 (models/v1/model.bin)
# 설명: 깊이가 깊어져도 계속 / 로 연결하면 됨
deep_file = BASE_DIR / "models" / "v1" / "model.bin"
print(f"3. 깊은 폴더 내 파일 경로:\n {deep_file}")
# [Bonus] 실제 파일이 존재하는지 체크
if deep_file.exists():
print("\n[확인] 모델 파일이 실제로 존재함")
else:
print(f"\n[주의] {deep_file} 위치에 파일 없음")핵심 포인트
BASE_DIR(기준점) 공식
Path(__file__).resolve().parent는 “나(이 코드 파일) 지금 어디 있지?”를 자동으로 계산해 기준점을 잡는 관용구임.
| 단계 | 반환값 예시 |
|---|---|
__file__ | "./main.py" 또는 상대 경로 |
Path(__file__) | WindowsPath('main.py') |
.resolve() | WindowsPath('C:/Users/me/MyProject/main.py') (절대 경로 + 심링크 해석) |
.parent | WindowsPath('C:/Users/me/MyProject') (파일이 든 폴더) |
/연산자
pathlib의 가장 큰 장점임. 문자열 더하기(+) 대신 나눗셈 기호(/)를 쓰면, 운영체제(Windows \, Unix /)에 맞춰 알아서 구분자가 결정됨.
- 직접 확인
print(type(BASE_DIR)) # <class 'pathlib.WindowsPath'> 또는 'pathlib.PosixPath'
print(BASE_DIR.is_absolute()) # True
print(BASE_DIR.exists()) # True이 구조를 쓰면 프로젝트 폴더를 통째로 다른 컴퓨터로 옮겨도 코드를 한 줄도 수정할 필요가 없음.
ospath
os.path 방식 (전통적, 레거시 호환용)
파이썬 3.4 이전이나 오래된 코드에서 가장 많이 보이는 방식임. 문자열 합치기(join) 기반이라 코드가 조금 더 길어짐.
import os
# 이렇게만 쓰면 "현재 파일과 같은 위치"의 datasets 폴더를 의미
DATASETS_DIR = os.path.join(os.path.dirname(__file__), "datasets")
# [Step 0] 기준점 잡기
# __file__ : 현재 파일
# abspath : 절대 경로 변환
# dirname : 폴더 경로만 추출
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
print(f"프로젝트 루트 경로: {BASE_DIR}")
print("-" * 30)
# [Case 1] 루트에 바로 있는 파일 (.env)
# 설명: os.path.join 함수 안에 기준 경로와 파일명을 쉼표로 나열
root_file = os.path.join(BASE_DIR, ".env")
print(f"1. 루트 파일 경로:\n {root_file}")
# [Case 2] 루트 > 폴더 > 파일 (config/settings.yaml)
# 설명: 폴더명과 파일명을 쉼표로 구분해 나열
folder_file = os.path.join(BASE_DIR, "config", "settings.yaml")
print(f"2. 폴더 내 파일 경로:\n {folder_file}")
# [Case 3] 루트 > 폴더 > 폴더 > 파일 (models/v1/model.bin)
# 설명: 깊이가 깊어지면 인자를 계속 추가
deep_file = os.path.join(BASE_DIR, "models", "v1", "model.bin")
print(f"3. 깊은 폴더 내 파일 경로:\n {deep_file}")dotenv
python-dotenv 방식 (보안 및 설정 분리)
경로/API 키/비밀번호 같은 값을 코드에 적지 않고, 외부 파일(.env)에 적어둔 뒤 불러오는 방식임. 팀원마다 폴더 구조가 다르거나, 배포 환경마다 값이 달라야 할 때 유용함.
준비물: .env 파일 (프로젝트 루트에 생성)
# .env 파일 내용
CONFIG_FILE_NAME="settings.yaml"
MODEL_DIR_NAME="models/v1"
DEEP_FILE_NAME="model.bin"파이썬 코드 (main.py)
import os
from dotenv import load_dotenv
from pathlib import Path
# [Step 0] .env 파일 로드 및 기준점 잡기
load_dotenv() # .env 내용을 환경변수로 주입
BASE_DIR = Path(__file__).resolve().parent
# [Case 1] .env 파일 자체의 경로
# 설명: .env 위치는 보통 고정이므로 직접 지정하거나 load_dotenv가 자동 탐색
root_file = BASE_DIR / ".env"
print(f"1. 루트 파일 경로:\n {root_file}")
# [Case 2] 루트 > 폴더 > 파일 (config/settings.yaml)
# 설명: 파일명만 .env에서 가져와 pathlib와 조합
config_name = os.getenv("CONFIG_FILE_NAME") # .env에서 값 조회
folder_file = BASE_DIR / "config" / config_name
print(f"2. 폴더 내 파일 경로 (설정값 이용):\n {folder_file}")
# [Case 3] 루트 > 폴더 > 폴더 > 파일 (models/v1/model.bin)
# 설명: 중간 경로도 .env에서 조립
model_dir = os.getenv("MODEL_DIR_NAME") # "models/v1"
model_name = os.getenv("DEEP_FILE_NAME") # "model.bin"
deep_file = BASE_DIR / model_dir / model_name
print(f"3. 깊은 폴더 내 파일 경로 (설정값 이용):\n {deep_file}")3가지 방식 한눈에 비교
| 관점 | pathlib (추천) | os.path (구식) | dotenv (설정 분리) |
|---|---|---|---|
| 코드 스타일 | BASE_DIR / "folder" / "file" | os.path.join(BASE_DIR, "folder", "file") | BASE_DIR / os.getenv("FILE") |
| 반환 타입 | Path 객체 (메서드 많음) | 단순 문자열 | 문자열 + 환경변수 |
| 가독성 | 매우 좋음 (직관적) | 보통 (괄호 많음) | 낮음 (.env 왕복 필요) |
| 목적 | 현대적인 파일 경로 조작 | 구버전 호환성 유지 | 비밀번호/경로 설정 분리 |
| 파이썬 버전 | 3.4+ | 모든 버전 | 별도 설치 필요 |
결론: 기본은 pathlib 사용, 보안이 필요한 API 키나 변경이 잦은 경로는 pathlib + dotenv 조합이 Best Practice임.
.parent parent
.parent 연쇄 사용 (깊은 폴더에서 탈출)
폴더가 깊숙이 있을 때, 프로젝트 최상위(Root)까지 올라가기 위해 .parent를 여러 번 연결해 씀.
📂 가정하는 폴더 구조
MyProject/ <-- (최상위 목표: ROOT)
│
├── data/
│ └── text.txt <-- (가져오려는 파일)
│
└── src/
└── utils/
└── file_loader.py <-- (현재 코드 위치: 깊음)
💻 파이썬 코드 (file_loader.py)
from pathlib import Path
# [Step 1] 현재 파일의 위치 구하기
# 결과: .../MyProject/src/utils/file_loader.py
current_file = Path(__file__).resolve()
# [Step 2] .parent를 연쇄적으로 사용하여 위로 올라가기
# .parent (1번): .../MyProject/src/utils (파일의 폴더)
# .parent (2번): .../MyProject/src (한 단계 위)
# .parent (3번): .../MyProject (두 단계 위 -> 프로젝트 루트!)
project_root = current_file.parent.parent.parent
print(f"프로젝트 루트: {project_root}")
# [Step 3] 루트에서 다시 아래로 내려가 파일 찾기
target_file = project_root / "data" / "text.txt"
print(f"타겟 파일 경로: {target_file}")
.parent횟수 계산법
.parent개수 = “현재 파일이 들어 있는 폴더 깊이” (루트 기준).src/utils/file_loader.py라면:file_loader.py→utils→src→MyProject로 3단계 올라가야 루트, 따라서.parent * 3.
Jupyter Notebook (.ipynb) 환경일 때
주피터에서는 __file__ 변수가 존재하지 않음. 사용하면 NameError 발생. 대신 Path.cwd() (Current Working Directory, 현재 작업 디렉토리)를 씀. 노트북 파일이 있는 폴더가 cwd가 됨.
핵심 차이: “파일(File)에서 출발하느냐, 폴더(Folder)에서 출발하느냐”.
가정하는 폴더 구조
Project/ <-- (우리가 가고 싶은 목적지: ROOT)
│
├── notebooks/ <-- (노트북 폴더)
│ └── analysis.ipynb <-- (현재 실행 중인 주피터 노트북)
│
└── src/ <-- (소스코드 폴더)
└── main.py <-- (현재 실행 중인 파이썬 파일)
왜 .parent 횟수가 다른가
파이썬 파일(.py)은 “나 자신(파일)” 에서 출발, 주피터 노트북(.ipynb)은 “내가 있는 방(폴더)” 에서 출발함.
| 구분 | 파이썬 스크립트 (.py) | 주피터 노트북 (.ipynb) |
|---|---|---|
| 명령어 | Path(__file__) | Path.cwd() |
| 출발점 | .../src/main.py (파일) | .../notebooks (폴더) |
| 1번 parent | .../src (파일 껍질 벗김) | .../Project (바로 상위 폴더) |
| 2번 parent | .../Project (상위 폴더) | (필요 없음) |
| 결론 | parent 2번 호출 | parent 1번 호출 |
단계별 상세 이동 경로
A. 파이썬 파일 (src/main.py)
파일 이름까지 포함된 경로에서 시작하므로, 파일명 떼기 작업이 한 번 더 필요함.
Path(__file__):.../Project/src/main.py(출발).parent(1번째):.../Project/src(파일명이 떨어져 나감).parent(2번째):.../Project(상위 폴더로 이동 → 도착)
B. 주피터 노트북 (notebooks/analysis.ipynb)
주피터의 cwd()는 현재 작업 폴더 경로만 줌. 파일명은 포함되지 않음.
Path.cwd():.../Project/notebooks(출발, 이미 폴더).parent(1번째):.../Project(상위 폴더로 이동 → 도착)
📂 노트북에서 데이터 읽기 예시
MyProject/
├── notebooks/
│ └── analysis.ipynb <-- (현재 실행 중인 노트북)
└── data/
└── dataset.csv <-- (가져오려는 데이터)
from pathlib import Path
# [중요] 노트북에서는 __file__ 대신 cwd() 사용
# 보통 노트북 파일이 있는 폴더가 현재 위치가 됨
current_dir = Path.cwd()
print(f"현재 노트북 위치: {current_dir}")
# [Step 1] 상위 폴더(MyProject)로 이동
# notebooks에서 나가야 하므로 .parent 한 번
project_root = current_dir.parent
print(f"프로젝트 루트: {project_root}")
# [Step 2] 데이터 파일 경로
data_path = project_root / "data" / "dataset.csv"
print(f"데이터 경로: {data_path}")
# 확인
import pandas as pd
# df = pd.read_csv(data_path)🚀 꿀팁: 스크립트와 노트북 양쪽에서 동작하는 만능 코드
노트북에서 실험하다가 .py 파일로 옮길 때마다 코드를 고치는 귀찮음을 없애는 관용구임.
from pathlib import Path
# 1. 실행 환경이 노트북인지 스크립트인지 판별해 루트 찾기
try:
# .py 파일로 실행될 때
PROJECT_ROOT = Path(__file__).resolve().parent.parent # 폴더 구조에 맞춰 .parent 개수 조절
except NameError:
# Jupyter Notebook에서 실행될 때 (__file__ 없음)
PROJECT_ROOT = Path.cwd().parent # 폴더 구조에 맞춰 .parent 개수 조절
# 2. PROJECT_ROOT 기준으로 경로를 잡으면 어디서든 동작함
data_dir = PROJECT_ROOT / "data"
model_dir = PROJECT_ROOT / "models"
print(f"설정된 루트 경로: {PROJECT_ROOT}")포함 관계
- 파이썬 표준 라이브러리
pathlib(객체지향 경로, 3.4+)PurePath(문자열 연산만, I/O 없음)PureWindowsPathPurePosixPath
Path(PurePath+ 파일시스템 I/O)WindowsPathPosixPath- 주요 메서드:
.resolve(),.parent,.exists(),.read_text(),.write_text(),.mkdir()
os.path(함수 기반, 레거시)os.path.join,os.path.dirname,os.path.abspath
- 외부 패키지
python-dotenv(.env파일을 환경변수로 주입)load_dotenv(),os.getenv()
관련 노트
- sys.path(python이 모듈을 찾을때 검색하는 디렉토리 경로)사용법 (임포트 경로 조작)
- 가상환경 세팅 방법 (경로 관리를 위한 가상환경 기초)