파이썬 코드를 프로젝트 루트에서 실행했을 때 이런 에러를 만난 적 있을 것입니다.
ModuleNotFoundError: No module named 'my_module'
분명히 같은 폴더에 파일이 있는데 왜 못 찾는 걸까요? 이걸 이해하려면 Python이 import를 처리하는 방식을 알아야 합니다.
import가 동작하는 원리Python은 import를 만나면 sys.path 라는 리스트를 참조합니다. sys.path는 Python이 모듈을 탐색할 폴더 경로들을 순서대로 담아둔 리스트로, 위에서부터 차례대로 뒤져서 해당 모듈을 찾습니다.
import sys
print(sys.path)
# 출력 예시
[
'/Users/me/myproject', # 현재 실행 위치
'/usr/lib/python3.11', # Python 표준 라이브러리
'/usr/lib/python3.11/site-packages', # pip/uv로 설치한 패키지들
...
]
찾으면 즉시 import하고, 리스트 끝까지 없으면 ModuleNotFoundError를 던집니다.
import my_module 실행
│
├─ 0번: /Users/me/myproject/ 에서 탐색 → 없음
├─ 1번: /usr/lib/python3.11/ 에서 탐색 → 없음
├─ 2번: site-packages/ 에서 탐색 → 없음
│
└─ ModuleNotFoundError !
myproject/utils/가 sys.path에 없으니까 거기 있는 파일을 못 찾는 것입니다. Python은 실행 시 스크립트 위치, 표준 라이브러리, site-packages만 자동으로 sys.path에 등록하기 때문입니다. myproject/utils/처럼 프로젝트 내부의 임의 폴더는 직접 추가해주지 않으면 탐색 대상이 되지 않습니다.
sys 모듈이란?sys는 Python 표준 라이브러리 중 하나로, Python 인터프리터 자체와 관련된 정보와 기능에 접근할 수 있게 해줍니다.
import sys
sys.path # 모듈 탐색 경로 리스트
sys.version # Python 버전
sys.argv # 실행 시 전달된 인자 리스트
sys.exit() # 프로그램 종료
이 중에서 sys.path가 오늘의 핵심입니다.
sys.path.insert(0, ...) 로 문제 해결하기탐색 경로에 우리 폴더를 직접 추가하면 됩니다.
import sys
sys.path.insert(0, 'myproject/utils/')
import my_module # 이제 찾을 수 있음!
여기서 insert(0, ...) 의 0은 "리스트 맨 앞에 넣어라" 라는 인덱스 번호입니다. myproject/utils/가 0번이 "되도록" 직접 집어넣는 것입니다.
sys.path.insert(0, '...') # 맨 앞 → 제일 먼저 탐색
sys.path.append('...') # 맨 뒤 → 제일 나중에 탐색
💡
append()로 뒤에 붙여도 기술적으로는 동작하지만, 맨 앞에 넣어야 다른 경로보다 먼저 탐색되어 이름 충돌 가능성을 없앨 수 있습니다.
pathlib과 Path란?그런데 위 코드에는 한 가지 문제가 있습니다. 경로를 하드코딩했다는 것입니다.
sys.path.insert(0, 'myproject/utils/') # 어디서 실행하느냐에 따라 달라짐!
프로젝트 루트가 아닌 다른 위치에서 실행하면 경로가 틀려버립니다. 이걸 해결해주는 것이 pathlib입니다.
pathlib은 Python 표준 라이브러리로, 파일 경로를 문자열 대신 객체로 다룰 수 있게 해줍니다.
# 옛날 방식 (os 모듈)
import os
print(os.path.dirname(os.path.abspath(__file__)))
# 요즘 방식 (pathlib)
from pathlib import Path
print(Path(__file__).parent)
결과는 같지만 pathlib이 훨씬 직관적입니다.
__file__ — 자기 자신의 경로__file__은 Python이 자동으로 제공하는 특수 변수로, 현재 실행 중인 파일의 경로를 담고 있습니다.
# main.py 안에서
print(__file__)
# → '/Users/me/myproject/utils/main.py'
어디서 실행하든 항상 자기 자신의 경로를 가리킵니다.
.parent — 부모 폴더 가져오기Path 객체의 .parent 속성은 해당 경로의 한 단계 위 폴더를 반환합니다.
from pathlib import Path
p = Path('/Users/me/myproject/utils/main.py')
p.parent # → PosixPath('/Users/me/myproject/utils')
p.parent.parent # → PosixPath('/Users/me/myproject')
Path 객체는 / 연산자로 경로를 이어붙일 수도 있습니다. Path 객체에 / 연산자로 경로를 이어붙일 때 오른쪽은 반드시 문자열이어야 합니다. 큰따옴표가 없으면 Python이 sample_docs를 변수 이름으로 읽어버려서 NameError가 납니다.
base = Path('/Users/me/myproject')
base / 'utils' / 'sample_docs'
# → PosixPath('/Users/me/myproject/utils/sample_docs')
문자열이었으면 base + '/utils/' + 'sample_docs' 이렇게 써야 했을 텐데, 훨씬 직관적입니다.
이제 모든 조각을 합칩니다.
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
한 줄씩 풀어보면 이렇습니다.
Path(__file__) # main.py 자기 자신의 경로
# → PosixPath('/Users/me/myproject/utils/main.py')
Path(__file__).parent # 그 파일이 있는 폴더
# → PosixPath('/Users/me/myproject/utils')
str(...) # sys.path는 문자열 리스트라서 변환 필요
# → '/Users/me/myproject/utils'
sys.path.insert(0, ...) # sys.path 맨 앞에 삽입
💡
__file__은 항상 자기 자신의 절대 경로를 담고 있기 때문에, 프로젝트 루트에서 실행하든 어느 폴더에서 실행하든.parent는 항상myproject/utils/를 정확하게 가리킵니다. 실행 위치에 흔들리지 않는 안정적인 import가 완성되는 것입니다.
import sys
from pathlib import Path
# ① 내 폴더를 탐색 경로 맨 앞에 추가 (선행 조건)
sys.path.insert(0, str(Path(__file__).parent))
# ② 이제 같은 폴더의 파일들을 import 가능
import my_module_a
import my_module_b
import my_module_c
sys.path.insert()가 선행 조건이라는 점이 핵심입니다. 이 한 줄이 없으면 바로 아래의 import 세 줄이 모두 실패합니다. 반드시 import 전에 경로를 등록해두어야 합니다.
---
## 10. `pathlib` 더 적극적으로 활용하기
`pathlib`의 진가는 경로 조합이 많아질수록 빛납니다.
```python
from pathlib import Path
BASE_DIR = Path(__file__).parent # 현재 파일의 폴더
SAMPLE_DIR = BASE_DIR / "sample_docs" # / 연산자로 경로 이어붙이기
OUTPUT_DIR = BASE_DIR / "output"
OUTPUT_DIR.mkdir(exist_ok=True) # 폴더 없으면 자동 생성
os 모듈 방식과 비교하면 차이가 명확합니다.
# os 방식
import os
base = os.path.dirname(os.path.abspath(__file__))
sample = os.path.join(base, 'sample_docs')
os.makedirs(sample, exist_ok=True)
# pathlib 방식
from pathlib import Path
base = Path(__file__).parent
sample = base / 'sample_docs'
sample.mkdir(exist_ok=True)
| 개념 | 한 줄 설명 |
|---|---|
sys | Python 인터프리터 관련 정보·기능 모듈 |
sys.path | import 탐색 경로 리스트 (위→아래 순서대로) |
sys.path.insert(0, ...) | 탐색 경로 맨 앞에 경로 추가 |
pathlib | 파일 경로를 객체로 다루는 표준 라이브러리 |
Path | 경로 객체 클래스 |
__file__ | 현재 실행 중인 파일의 경로 (특수 변수) |
.parent | 한 단계 위 폴더 반환 |
/ 연산자 | Path 객체끼리 경로 이어붙이기 |
한 줄 요약:
sys.path.insert(0, str(Path(__file__).parent))는 "어느 폴더에서 실행하든 항상 내 옆에 있는 모듈을 찾을 수 있게" 보장하는 패턴입니다.