Backend/Trouble Shooting

[Python 에러 분석] 직접 구현한 모듈을 import할 때 ModuleNotFoundError가 발생하는 이유

sangwonYoon 2023. 7. 4. 19:03

모델 추론 코드를 테스트하기 위한 코드를 작성하던 도중 만난 ModuleNotFoundError를 해결하면서 이해한 python이 모듈을 import하는 방식에 대한 내용을 공유해보려고 한다.

 

문제 상황

에러가 발생한 디렉토리 구조는 아래와 같았다.

/Users/sangwon/Documents/GitHub/mlops_practice
├── tests
│   └── test_inference.py
└── webapp
    ├── dataset
    │   ├── datasets.py
    │   ├── preprocess.py
    │   └── tokenize.py
    ├── inference.py
    ...

 

inference.py는 datasets.py를 import하고 있고, inference.py를 단독적으로 실행할 때는 문제 없이 실행되는 상황이었다.

# inference.py

from dataset.datasets import make_dataset

...

 

inference.py를 테스트하기 위한 test_inference.py 코드는 아래와 같이 inference.py를 import하도록 구현했다.

# test_inference.py

import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))

from webapp.inference import inference


def test_inference():
    ...

 

이 상황에서 pytest를 실행했을 때 발생한 에러는 아래와 같았다.

webapp/inference.py:6: in <module>
    from dataset.datasets import make_dataset
E   ModuleNotFoundError: No module named 'dataset'

 

문제 해결

이 문제를 해결하기 위해서는 python이 모듈을 import하는 원리와 sys.path에 대한 이해가 필요하다

 

Python이 모듈을 import하는 원리

python은 sys.path에 정의된 경로에서 모듈을 찾는다.

예를 들어, sys.path가 [”A”, “B”, “C”]라고 할 때, import inference라는 코드를 만나면, inference 모듈을 찾기 위해 sys.path에 정의되어 있는 첫 경로부터 순차적으로 탐색한다.(이 때 “A”, “B”, “C”는 경로를 나타낸다.)

만약 A 디렉토리에 inference 모듈이 존재하면, 해당 모듈을 import한 뒤 탐색을 종료한다. 따라서 B 디렉토리에도 inference 모듈이 존재하더라도, sys.path의 순서에 의해 B 디렉토리 아래에 있는 inference 모듈은 importe되지 않는다.

만약 sys.path에 정의된 모든 경로에서 inference 모듈을 찾을 수 없다면, ModuleNotFoundError를 발생시킨다.

 

sys.path

sys.path는 아래의 순서로 구성된다.

  1. 최초로 실행된 python 스크립트가 속한 디렉토리의 경로
  2. PYTHONPATH 환경 변수의 값
    PYTHONPATH 환경 변수에 경로를 추가하여 sys.path의 값을 조작할 수 있다.
  3. OS와 python이 사전에 정의한 기본 경로
    python의 내장 모듈이나 외부 라이브러리를 import하기 위한 경로이다.

sys.path의 경로가 위에서 언급한 순서대로 저장되어 있기 때문에, 내장 모듈과 직접 구현한 모듈의 이름이 동일할 경우, 직접 구현된 모듈이 우선해서 import된다. (단, 직접 구현한 모듈이 최초로 실행된 python 스크립트와 같은 디렉토리에 위치해야 한다.)

sys.path의 값이 실행하는 python 스크립트의 위치에 따라 변하기 때문에 의도치 않은 ModuleNotFoundError를 마주칠 수 있다.

내가 겪은 문제 상황으로 돌아가보면, inference.py를 실행할 때의 sys.path 값은 다음과 같다.

['/Users/sangwon/Documents/GitHub/mlops_practice/webapp', ...]

 

inference.py dataset 디렉토리가 같은 webapp 디렉토리에 위치해 있기 때문에 from dataset.datasets import make_dataset 문제 없이 동작할 수 있었다.

 

그러나, test_inferenece.py를 실행할 때의 sys.path의 값은 다음과 같다.

['/Users/sangwon/Documents/GitHub/mlops_practice/tests', ...]

tests 디렉토리 하위에 dataset 디렉토리가 위치하지 않기 때문에 ModuleNotFoundError가 발생한 것이다. 따라서 이 문제를 해결하기 위해서는 inference.py가 속한 디렉토리의 경로를 sys.path에 추가해줘야 한다.

# test_inference.py

import os
import sys

sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))

webapp_path = os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))), "webapp")
sys.path.append(webapp_path) # webapp 디렉토리 경로 추가

from webapp.inference import inference


def test_inference():
    ...

테스트 코드에서 발생한 ModuleNotFoundError를 해결하기 위해 import의 동작 원리와 sys.path의 구성 요소들을 찾아보며 그동안 부모 디렉토리의 모듈을 import하기 위해 기계적으로 sys.path.append를 사용하면서 어렴풋이 알고 있었던 개념을 확실하게 짚고 넘어갈 수 있었다.

 

(잘못된 내용을 지적해주시거나 내용에 관한 피드백은 언제나 환영입니다!)