본문 바로가기

Computer/Python

ModuleNotFoundError 와 ImportError

반응형

큰 프로젝트를 구현하다보면 비슷한 기능을 수행하는 클래스와 함수를 별도의 디렉토리, 파일로 refactoring 하고 메인 디렉토리에서 main.py (혹은 test.py)를 수행하게 됩니다. 보통 파이썬 패키지를 구현하면 메인 working directory 에서 소스코드가 담긴 src 디렉토리, 예시가 담긴 examples 디렉토리, 테스트를 위한 tests 디렉토리를 구성하게 되는데요, src 디렉토리 이름을 Figure 1과 같이 working 디렉토리 이름과 같이 정하는 경우도 흔히 있습니다. Figure 1은 HongLearning이란 패키지 예시이고 소스 코드를 HongLearning 디렉토리 안에 구현했다고 가정했을 때의 디렉토리 트리 구조입니다.

Figure 1

현재 current working directory (pwd)는 다음과 같고

>>> import os
>>> os.getcwd()
'C:\\Users\\yukua\\PycharmProjects\\HongLearning'

테스트를 위해 tests 폴더에 hello_test.py 파일을 만들어 실행시킵니다.

from HongLearning import hello

if __name__ == "__main__":
    hello(whom='Hong')

하지만 터미널에서 실행시키면 ModuleNotFoundError가 발생합니다. 즉, HongLearning 이라는 모듈을 못 찾았다는 것이죠.

(venv) C:\Users\yukua\PycharmProjects\HongLearning>python tests/hello_test.py
Traceback (most recent call last):
  File "tests/hello_test.py", line 1, in <module>
    from HongLearning import hello
ModuleNotFoundError: No module named 'HongLearning'

이유는 간단합니다. 파이썬 인터프리터는 파이썬 파일을 실행할 때 실행시키는 파이썬 파일이 위치한 경로를 sys.path에 자동으로 추가시키고 임포트할 모듈 이름을 built-in modules, sys.path 순으로 찾게 됩니다. 하지만 현재 hello_test.py 파일은 tests 폴더 안에 위치해 있고 HongLearning 이라는 모듈은 부모 디렉토리에 위치해 있기 때문에 모듈을 발견할 수 없습니다. 따라서 sys.path 에 HongLearning 모듈이 위치한 경로, 즉 부모 디렉토리 ('..')를 추가하면 쉽게 해결할 수 있습니다.

import sys
sys.path.append('.')

from HongLearning import hello

if __name__ == "__main__":
    hello(whom='Hong')
    
    
(venv) C:\Users\yukua\PycharmProjects\HongLearning>python tests/hello_test.py
Hello Hong
  • 위 코드를 보면 sys.path에 부모 경로 ('..')가 아닌 현재 경로 ('.')를 추가했습니다. 왜일까요? 파이썬 파일을 실행시키는 현재 working directory 자체는 HongLearning 모듈이 위치한 경로이기 때문입니다.
  • 따라서 tests 폴더로 이동한 이후에 hello_test.py를 실행시키면 에러가 발생합니다.
    (venv) C:\Users\yukua\PycharmProjects\HongLearning>cd tests
    
    (venv) C:\Users\yukua\PycharmProjects\HongLearning\tests>python hello_test.py
    Traceback (most recent call last):
      File "hello_test.py", line 4, in <module>
        from HongLearning import hello
    ModuleNotFoundError: No module named 'HongLearning'​
  • 이때는 sys.path에 부모 경로 ('..')를 추가하면 됩니다.
    import sys
    sys.path.append('..')
    
    from HongLearning import hello
    
    if __name__ == "__main__":
        hello(whom='Hong')
        
        
    (venv) C:\Users\yukua\PycharmProjects\HongLearning\tests>python hello_test.py
    Hello Hong​

패키지 이름과 소스코드가 담긴 디렉토리 이름이 같음으로써 발생하는 또다른 문제는 ImportError 입니다. sys.path에 부모 경로만 추가시켜놓고 실행시키면 다음과 같이 HongLearning 이라는 이름은 발견했지만 모듈의 api를 찾지 못하는 ImportError가 발생합니다.

import sys
sys.path.append('..')

from HongLearning import hello

if __name__ == "__main__":
    hello(whom='Hong')
    
    
(venv) C:\Users\yukua\PycharmProjects\HongLearning>python tests/hello_test.py
Traceback (most recent call last):
  File "tests/hello_test.py", line 4, in <module>
    from HongLearning import hello
ImportError: cannot import name 'hello' from 'HongLearning' (unknown location)

이 에러가 발생하는 이유는 우리는 소스코드가 담긴 HongLearning 모듈이 담기기를 원하지만 실제로는 패키지 위의 디렉토리가 sys.path에 추가되어버렸기 때문입니다. 실제로 sys.path를 확인해보면 있어야 할 'C:\\Users\\yukua\\PycharmProjects\\HongLearning' 경로는 담기지 않고 dir 함수를 통해 HongLearning의 변수들을 확인하보면 hello 함수이름이 존재하지 않습니다.

import sys
sys.path.append('..')
print(sys.path)

import HongLearning
print(dir(HongLearning))
(venv) C:\Users\yukua\PycharmProjects\HongLearning>python tests/hello_test.py
['C:\\Users\\yukua\\PycharmProjects\\HongLearning\\tests', 'C:\\Users\\yukua\\AppData\\Local\\Programs\\Python\\Python38\\python38.zip', 'C:\\Users\\yukua\
\AppData\\Local\\Programs\\Python\\Python38\\DLLs', 'C:\\Users\\yukua\\AppData\\Local\\Programs\\Python\\Python38\\lib', 'C:\\Users\\yukua\\AppData\\Local\
\Programs\\Python\\Python38', 'C:\\Users\\yukua\\PycharmProjects\\HongLearning\\venv', 'C:\\Users\\yukua\\PycharmProjects\\HongLearning\\venv\\lib\\site-pa
ckages', '..']
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']

ModuleNotFoundError와 ImportError를 동시에 해결하는 방법은 sys.path에 현재 경로와 부모 경로를 모두 추가해주는 것입니다. 그렇다면 현재 working directory가 tests 디렉토리나 메인 디렉토리에 위치해 있더라도 경로 임포트로 인한 문제가 발생하지 않습니다.

import sys
[sys.path.append(i) for i in ['.', '..']]

from HongLearning import hello

if __name__ == "__main__":
    hello(whom='Hong')
반응형

'Computer > Python' 카테고리의 다른 글

정규표현식을 이용해 문자열에서 숫자 찾기  (0) 2022.03.25
Property와 descriptor (디스크립터)  (0) 2021.08.27
Vectorization  (0) 2021.08.14
"is" vs "=="  (0) 2021.08.11
파이썬 실수 내림/올림  (0) 2021.08.03