src
├── calc
│ ├── __init__.py
│ └── calculator.py
├── client
│ └── client.py
│ └── sys_path_test.py
def add(a: int, b: int)-> int:
return a + b
# 절대 경로 참조
from calc.calculator import add
print(__name__)
def calc_sum():
a = 1
b = 2
print(add(a,b))
calc_sum()
$ python client/client.py
ModuleNotFoundError: No module named 'calc'
모듈 calc 를 찾지 못한다.
왜 그럴까?
파이썬은 실행 대상 파일의 패키지를 Current Working Directory(CWD) 이자 top-level script 로인식한다.
python client/client.py실행시
client를 CWD , top-level package로 인식한다.
-> from calc.calculator 구문은 프로젝트 src/calc 대신 client/calc 를 참조 하려한다.
-> 참조 실패: ModuleNotFoundError
Q. 이상한데요? 그럼 엉뚱한 곳에 위치해서 실행시키면요?
A. 무적권 실행 인자 (대상 파일)가 위치한 디렉토리가 곧 sys.path 의 첫번째 값이 된다.
sys.path 는 뭐야?import 할 때 모듈을 찾을 경로를 저장해둔 리스트.
OS에 환경변수 PATH가 있다면
Python 에는 모듈 디렉토리를 모아둔 sys.path 가 있다.
import sys
print(sys.path)
위 코드를 실행시켜보자.
$ pwd
%USER_HOME%/my_project
$ python src/sys_path_test.py
[
'C:\\Users\\m_falocn\\IdeaProjects\\python-practice\\src\\module\\client',
'C:\\Users\\m_falocn\\AppData\\Local\\Programs\\Python\\Python313\\python313.zip',
'C:\\Users\\m_falocn\\AppData\\Local\\Programs\\Python\\Python313\\DLLs',
'C:\\Users\\m_falocn\\AppData\\Local\\Programs\\Python\\Python313\\Lib',
'C:\\Users\\m_falocn\\AppData\\Local\\Programs\\Python\\Python313'
]
파이썬은
sys.path리스트의 0번째 원소부터 차례대로 해당 모듈을 찾는다.
즉, 위 결과에 따르면
1. python-practice\\src\\module\\client
2. AppData\\Local\\Programs\\Python\\Python313\\python313.zip
3. AppData\\Local\\Programs\\Python\\Python313\\DLLs,
4. AppData\\Local\\Programs\\Python\\Python313\\Lib
5. AppData\\Local\\Programs\\Python\\Python313
위 순서대로 모듈을 찾고 없으면 ModuleNotFoundError 를 뱉는다.
sys.path의 첫 번째 값은 Python 스크립트를 실행한 CWD 가 아닌, 그 스크립트가 위치한 디렉터리가 된다.
위 코드를 실행시켜보자.
$ pwd
%USER_HOME%/my_project
$ python src/sys_path_test.py
$ pwd
%USER_HOME/src
$ python sys_path_test.py
둘다 같은 결과를 갖는다.
Root 가 되는 package다.
sound/ Top-Level Package
main.py
formats/
a.py
effects/
b.py
filters/
c.py
안타깝게도 아래와 같이 실행하면
formats/a.py 에서 effects/b.py 참조가 불가능하다.
from ..effects.b import b_method
b_method()
모듈은 절대 top-level package 를 넘어서서 참조할 수 없다.
⚠️ ImportError: attempted relative import beyond top-level package
sound 가 Top-Level Package 로 인식되지 않기 때문이다.
파이썬 인터프리터는 sound 패키지가 존재한다는 사실조차 모른다.
왜 인식을 못하지?
이를 이해하기 위해선 top-level script, __main__ 이 무엇인지 알아야한다.
__main__# main.py 는 top-level code 다.
$ python main.py
python 실행 대상 스크립트를 Top-level code 라고 한다.
top-level code 외에 top-level script, EntryPoint 라고도 부른다.
top-level code 는 항상 __name__ 값을 __main__ 으로 갖는다.
print(__name__) # '__main__'
참조: python - main docs, Python - package reference docs
__name__ 은 패키지명 + 모듈명이다.예제를 통해 알아보자.
sound/
main.py
formats/
a.py
effects/
b.py
filters/
c.py
print(__name__) # effects.b
실행 결과effects.b 에서 effects 는 패키지명 b는 모듈명이다.
이처럼 __name__은 <package-name>.<package-name>.<module-name> 형태로 패키지명과 모듈명을 모두 갖는다.
복습해보자. top-level script 는 특수한 __name__ 을 갖는다.
$ python main.py
print(__name__) # __main__
실행 결과가 __main__이 된다.
어? __name__ 곧 패키지명 + 모듈명이라 했다.
Q. 그럼 이녀석의 패키지명은 무엇인가?
A. 없다!
즉, 요녀석은 패키지명 없이 오로지 특수 모듈명 __main__ 을 갖는다.
top-level script , EntryPoint 는
__name__값이__main__로 잡힌다.
패키지명이 없다. 오로지 특수 모듈명만 갖는다.
-> 패키지명을 파이썬 인터프리터가 해석할 수 없다.
눈치 챘는가? 이 에러의 원인은
"패키지명을 파이썬 인터프리터가 해석할 수 없음" 이다.
sound/
main.py
formats/
a.py
effects/
b.py
filters/
c.py
$ python main.py
파이썬 인터프리터가 sound 라는 패키지명을 알아낼 재간이 없다.
effects/b.py 에서 .. 상대경로로 이동하고 다시 formats/a.py 로 가려면
도중에 sound 라는 패키지가 인식되어야 한다.
ex) ..effects -> sound -> sound/formats
근데 파이썬 인터프리터가 sound 의 존재를 알 수 없다.
그래서 이런 에러가 나는거다.
ImportError: attempted relative import beyond top-level package
-m 옵션은 무슨 용도인가?$ python -m client.client # 정상 실행
-m 옵션 추가시 파이썬이 현재 디렉토리(CWD)를 sys.path 첫 번째 원소로 추가한다.
또, 이를 top-level package 로 간주한다.
Q. 이 경우엔 top-level package 가 어디로 인식되나요?
A. 실행 위치인 src 가 된다.
따라서 src/calc 를 정상 참조한다.
-m 옵션과 함께 코드를 다시 실행시켜보자.
$ pwd
%USER_HOME%/python-practice
$ python -m src.client.sys_path_test
[
'C:\\Users\\m_falocn\\IdeaProjects\\python-practice\\src',
'C:\\Users\\m_falocn\\AppData\\Local\\Programs\\Python\\Python313\\python313.zip',
'C:\\Users\\m_falocn\\AppData\\Local\\Programs\\Python\\Python313\\DLLs',
'C:\\Users\\m_falocn\\AppData\\Local\\Programs\\Python\\Python313\\Lib',
'C:\\Users\\m_falocn\\AppData\\Local\\Programs\\Python\\Python313'
]
-m 옵션이 없다면 sys_path_test.py 가 위치한 \\src\\client 가 첫 경로가 된다.
-m 옵션을 실행하니 CWD 인 src 가 sys.path 의 첫번째 경로로 지정됐다.
반복한다.
-m옵션 추가시 현재 디렉토리(CWD)가 곧sys.path에 추가되어 top-level package 가 된다.
__init__ 은 무슨 용도인가?디렉토리를 패키지로 인식할 수 있게하는 파일이다.
__name__ 은 왜, 언제 쓰나?ImportError 를 피하고 싶다면, main.py 를 프로젝트 루트로 빼고 나머지 모듈은 상대경로 참조를 써라.
이게 나의 결론이다.
나는 절대경로를 구구절절 다 쓰는 것보단, 상대경로 참조를 선호한다.
sound/
main.py
formats/
a.py
effects/
b.py
filters/
c.py
main.py 를 루트로 빼자.
main.py
sound/
formats/
a.py
effects/
b.py
filters/
c.py
이제 python main.py를 실행할 때 sound 를 찾을 수 있게되었다.