PYTHON - 모듈 절대경로&상대경로 import 에러 해결 방법. (절대 경로? 상대 경로?)

Problem

파이썬의 패키지(package)는 모듈(ex. “test.py”, “date_utils.py”)이나 하위 디렉토리 (ex. utils/)를 계층 구조로 관리할 수 있게 해준다. 즉, 디렉토리에 접근하는 것과 유사하게 “.”(dot)으로 패키지 내 모듈과 하위 디렉토리에 접근할 수 있다.

└── test
    ├── __init__.py
    ├── test.py
    └── utils
        └── date_utils.py

예를 들어 test.py 모듈에서 utils/date_utils.py에 있는 함수를 사용하고 싶다고 하자.

# test/test.py
_script_path = os.path.abspath(os.path.dirname(__file__)) # test.py의 절대 경로
_util_path = os.path.abspath(os.path.join(_script_path, 'utils')) # utils 디렉토리의 절대경로
sys.path.insert(0, _util_path) # utils 절대경로를 환경변수에 추가
from date_utils import * # date_util 모듈 import

이렇게 파이썬 환경 변수 (sys.path)에 모듈의 절대 경로를 추가하면 from (module_name) import (function) 코드로 모듈을 불러올 수 있다.

하지만 아래와 같은 상황에서는 환경 변수가 꼬이는 문제가 발생한다. 이번엔 test.py 모듈에서 utils/date_utils.py에 있는 함수 뿐만 아니라 util_old/date_uils.py에 있는 함수도 사용하고 싶다고 하자. 이렇게 서로 다른 두 디렉토리에 이름이 동일한 모듈이 존재할 때 문제가 생긴다.

└── test
    ├── __init__.py
    ├── test.py
    ├── util_old
    │   └── date_utils.py
    └── utils
        └── date_utils.py
# test/test.py
_script_path = os.path.abspath(os.path.dirname(__file__)) # test.py의 절대 경로
_util_path = os.path.abspath(os.path.join(_script_path, 'utils')) # utils 디렉토리의 절대경로
_util_old_path = os.path.abspath(os.path.join(_script_path, 'util_old')) # util_old 디렉토리의 절대경로

sys.path.insert(0, _util_path) # utils 절대경로를 환경변수에 추가
from date_utils import * # date_util 모듈 import

sys.path.insert(0, _util_old_path) # util_old 절대경로를 환경변수에 추가
from date_utils import * # date_util 모듈 import

sys.path에는 utils의 절대 경로와 util_old의 절대 경로가 모두 추가되었다. 따라서 date_utils모듈의 위치는 utils의 절대 경로가 될 수도 있고, util_old의 절대 경로가 될 수도 있다.

따라서 파이썬 환경 변수(sys.path)에 모듈이 위치한 절대 경로를 추가하는 방법은 모듈 이름이 겹치는 경우에 안정성을 보장할 수 없다는 문제가 발생한다.

Solution

파이썬 패키지는 “.”(dot)을 이요해 계층 구조를 관리한다고 했다. 따라서 패키지 내 모듈에 접근할 때 “.”(dot)을 이용해 모듈의 상대 경로(메인 모듈에 따라 다름) 접근할 수 있다.

메인 모듈

  • 인터프리터에서 실행하는 모듈
  • __name____main__으로 설정됨

메인 모듈은 인터프리터에서 직접 실행하는 파일로 현재 예시에서는 test.py모듈이 메인 모듈이 된다.

# test/test.py
print(__name__)

위의 test.py을 실행하면 아래와 같은 결과물이 나온다.
메인 모듈의 __name__변수는 항상 __main__값을 갖는다.

python3 test/test.py
>>> __main__

절대 경로

메인 모듈에서는 절대 경로로 모듈을 import 해야 한다.

# test/test.py
from utils.date_utils import * # utils/date_utils.py 모듈의 모든 함수를 가져옴

만약 아래와 같이 메인 모듈에서 상대 경로를 사용하면 importerror가 발생한다.

# test/test.py
from .utils.date_utils import * # utils/date_utils.py 모듈의 모든 함수를 가져옴

“parent package”가 정의되지 않아 상대경로를 사용할 수 없다는 에러이다.

Reference

Python-ImportError-attempted-relative-import-with-no-known-parent-package sys-path-pythonpath jumptopython