운영체제와 상관없는 경로 설정

한마디 요약

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} 위치에 파일 없음")

핵심 포인트

  1. BASE_DIR (기준점) 공식

Path(__file__).resolve().parent는 “나(이 코드 파일) 지금 어디 있지?”를 자동으로 계산해 기준점을 잡는 관용구임.

단계반환값 예시
__file__"./main.py" 또는 상대 경로
Path(__file__)WindowsPath('main.py')
.resolve()WindowsPath('C:/Users/me/MyProject/main.py') (절대 경로 + 심링크 해석)
.parentWindowsPath('C:/Users/me/MyProject') (파일이 든 폴더)
  1. / 연산자

pathlib의 가장 큰 장점임. 문자열 더하기(+) 대신 나눗셈 기호(/)를 쓰면, 운영체제(Windows \, Unix /)에 맞춰 알아서 구분자가 결정됨.

  1. 직접 확인
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.pyutilssrcMyProject3단계 올라가야 루트, 따라서 .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)

파일 이름까지 포함된 경로에서 시작하므로, 파일명 떼기 작업이 한 번 더 필요함.

  1. Path(__file__): .../Project/src/main.py (출발)
  2. .parent (1번째): .../Project/src (파일명이 떨어져 나감)
  3. .parent (2번째): .../Project (상위 폴더로 이동 → 도착)

B. 주피터 노트북 (notebooks/analysis.ipynb)

주피터의 cwd()는 현재 작업 폴더 경로만 줌. 파일명은 포함되지 않음.

  1. Path.cwd(): .../Project/notebooks (출발, 이미 폴더)
  2. .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 없음)
        • PureWindowsPath
        • PurePosixPath
      • Path (PurePath + 파일시스템 I/O)
        • WindowsPath
        • PosixPath
        • 주요 메서드: .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()

관련 노트