Search
🌍

파이썬 CLI의 -m 플래그와 __package__: import 에서 자꾸 오류가 나는 대부분의 이유

프로젝트
♻️ prev note
♻️ next note
16 more properties
파이썬 개발을 할 때 임포트 오류를 접한 적이 정말 많을 것이다. 파이썬의 임포트 논리는 직관적이게 느껴지면서도 때로 이해할 수 없는 방식으로 작동하는 것처럼 보이곤 한다. 체감상 그 이유 중 90%는 PYTHONPATH 을 잘못 설정해서 생기는 문제이거나 패키지에 대해 알고 있는 개념이 부족하기 때문이다. 이 글에서 다룰 패키지 작동 방식을 한번 잘 정리해 이해해 두는 것은 파이썬 프로젝트의 구조를 이해하기 쉽게 설계하는 일에 아주 큰 도움이 된다. 부끄럽지만 나도 이 내용을 깔끔하게 정리된 상태로 머리에 넣고 있지는 않았기 때문에, 이참에 공유할 겸 정리한다. 이 글의 독자는 아래 질문에 명확히 답할 수 없는 사람들이다.
다음과 같은 디렉토리 구조를 가진 파이썬 프로젝트가 있다. 메인 함수가 위치한 a.py 에서는 b.py 를 임포트하고자 한다. a.py어떻게 작성해서 어떻게 실행해야 오류가 나지 않을까? 모두 골라보자.
. (터미널 명령어 실행 위치) └── A1 ├── A2 │ └── a.py └── B2 └── b.py
Python
복사
디렉토리 구조
1.
a.pyfrom ..B2 import b 를 작성한다. python3 A1/A2/a.py 로 실행한다.
2.
a.pyfrom ..B2 import b 를 작성한다. python3 -m A1.A2.a 로 실행한다.
3.
a.pyfrom A1.B2 import b 를 작성한다. python3 A1/A2/a.py 로 실행한다.
4.
a.pyfrom A1.B2 import b 를 작성한다. python3 -m A1.A2.a 로 실행한다.
5.
어떠한 것도 정상적으로 작동하지 않는다.
파이썬에서 스크립트나 모듈을 실행하는 방법에는 여러 가지가 있지만, -m 플래그를 사용하는 방식은 이러한 측면에서 특별한 의미를 가진다. 이 글에서는 -m 플래그의 중요성과 그 사용 시 발생하는 동작 방식의 차이를 설명한다. 또한, -m 플래그 없이 실행할 때 발생할 수 있는 문제점과 그 해결책을 제시한다.
-m 플래그를 사용하는 경우, 파이썬 인터프리터는 모듈이나 패키지를 임포트한 다음 스크립트를 실행한다. 그게 아니라면 그냥 스크립트를 실행한다'(ref1). 예를 들어 A1 라는 폴더 안에 A2 라는 폴더가 있고 a.py 모듈이 있다고 생각해 보자. python3 A1/A2/a.py 로 실행하면 그냥 스크립트 자체가 실행될 뿐이지만, python3 -m A1.A2.a 로 실행하면 a.py 의 내용을 실행하기 전에 import A1.A2.a 를 자동으로 실행한다는 것이다.
. (터미널 명령어 실행 위치) └── A1 └── A2 └── a.py
Python
복사
디렉토리 구조
이것이 도대체 어떤 의미를 가지는 것일까? 파이썬에서 패키지의 정의는 모듈의 집합이다(ref3). 그말인즉, 단일 파이썬 스크립트는 기본적으로는 그 자체로 패키지라고 여겨지지 않는다는 것이다(ref2). 스크립트가 실행될 때 __package__ 변수는 스크립트가 어떤 패키지에 속해 있는지를 나타낸다. 만약 이 변수가 비어 있다면, 파이썬 스크립트는 어떤 패키지에도 속하지 않는 것으로 간주된다. 만약 -m 플래그를 지정하지 않으면 실행 중인 스크립트에 __package__ 변수가 지정되지 않는다.
print('a.py 를 찾았습니다.') print(__package__) if __name__ == '__main__': print('a.py main 을 실행합니다.')
Python
복사
a.py
위와 같이 a.py 를 작성하고 두 가지 방법으로 실행했을 때 결과가 어떻게 달라지는지 확인해보자.
a.py 를 찾았습니다. A1.A2 a.py main 을 실행합니다.
Python
복사
python3 -m A1.A2.a 실행 결과
a.py 를 찾았습니다. None a.py main 을 실행합니다.
Python
복사
python3 A1/A2/a.py 실행 결과
__package__ 변수가 지정되지 않아 발생하는 문제를 설명하기 위해 이번에는 파일 b.py 가 있다고 상상해 보자.
. (터미널 명령어 실행 위치) └── A1 ├── A2 │ └── a.py └── B2 └── b.py
Python
복사
디렉토리 구조
동일한 위치에서 -m 없이 실행하는 경우, a.py 파일에서 A1/B2/b.py 를 임포트하기 위해 상대경로 임포트 from ..B2 import b 를 실행하는 경우 오류가 발생한다.
print('a.py 를 찾았습니다.') print(__package__) from ..B2 import b if __name__ == '__main__': print('a.py main 을 실행합니다.')
Python
복사
a.py
a.py 를 찾았습니다. None Traceback (most recent call last): File "/Users/janghoo/dev/python-import/A1/A2/a.py", line 3, in <module> from ..B2 import b ImportError: attempted relative import with no known parent package
Python
복사
python3 A1/A2/a.py 실행 결과. python3 -m A1.A2.a 은 오류없이 실행된다.
그 이유는 오류가 의미하듯 .. 라는 것은 패키지 내에서 부모를 의미하는데 패키지 정보가 없기 때문이다. __package__ 변수가 비어 있다는 것이다.
from A1.B2 import b 를 실행해도 아래와 같은 오류가 발생한다.
print('a.py 를 찾았습니다.') print(__package__) from A1.B2 import b if __name__ == '__main__': print('a.py main 을 실행합니다.')
Python
복사
a.py
a.py 를 찾았습니다. None Traceback (most recent call last): File "/Users/janghoo/dev/python-import/A1/A2/a.py", line 3, in <module> from A1.B2 import b ModuleNotFoundError: No module named 'A1'
Python
복사
python3 A1/A2/a.py 실행 결과. python3 -m A1.A2.a 은 오류없이 실행된다.
오류는 파이썬 인터프리터는 A1/A2 의 위치에서 또다시 A1 을 찾고 있는 상황임을 시사한다. A1/A2 의 위치에서 또다시 A1 을 찾는 이유는 파이썬이 터미널 실행 위치와 상관없이 PYTHONPATH 에 자동으로 a.py 파일의 위치를 추가하기 때문이다.
-m 플래그의 사용은 파이썬에서 모듈과 스크립트를 실행할 때 중요한 차이를 만든다. 이 플래그를 사용하면 __package__ 변수가 적절히 설정되어 임포트 기능이 정상적으로 작동한다. 여러 개의 모듈로 구성된 파이썬 프로젝트를 개발 중이라면 -m 플래그의 사용처를 꼭 이해해 두고, 이를 고려해 프로그램 진입점과 프로젝트 구조를 설계하자.
글 최상단 문제의 정답은 2, 4이다.
parse me : 언젠가 이 글에 쓰이면 좋을 것 같은 재료을 보관해 두는 영역입니다.
1.
None
from : 과거의 어떤 원자적 생각이 이 생각을 만들었는지 연결하고 설명합니다.
1.
sys.path(PYTHONPATH), __init__.py 의 역할에 대해서는 앞의 글을 참고하자.
supplementary : 어떤 새로운 생각이 이 문서에 작성된 생각을 뒷받침하는지 연결합니다.
1.
None
opposite : 어떤 새로운 생각이 이 문서에 작성된 생각과 대조되는지 연결합니다.
1.
None
to : 이 문서에 작성된 생각이 어떤 생각으로 발전되거나 이어지는지를 작성하는 영역입니다.
ref : 생각에 참고한 자료입니다.