규도자 개발 블로그

파이썬 스크립트 terminal에서 실행할 때 ModuleNotFoundError 해결하기 본문

Python/Python

파이썬 스크립트 terminal에서 실행할 때 ModuleNotFoundError 해결하기

규도자 (gyudoza) 2022. 1. 9. 11:49

파이썬 스크립트 terminal에서 실행할 때 ModuleNotFoundError 해결하기

어떤 프로젝트에서 cron이나 migrate tool 등을 커스텀해서 만들어놨다고 시나리오를 짜보자. 이건 프로젝트를 실행할 때 동시에 실행되는 것이 아니라 독립적인 프로세스를 가지고 실행돼야하는 것이다. 구체적인 예를 들어보자.

 

이건 어떤 framework로 작성한 프로젝트 혹은 뭔가 만들기 위해 체계적으로 작성한 프로젝트가 아니라 내가 그냥 개인적으로 필요한 작업들을 파이썬으로 작성해놓은 것이다. 개중에는 크롤러도 있고, api요청으로 정보를 가져오는 것 그리고 데이터베이스에 쓰는 것, 엑셀로 내뱉는 것 등등 아주 많다. 이건 하나의 프로젝트로 동작하는 프로그램이 아니라 그냥 내가 필요할 때마다 이것저것 실행하는 형태이므로 사실상 각 파일이 터미널창에서 실행했을 때 온전하게 실행돼야할 필요가 있었는데 그때 나를 만난 에러가 바로 ModuleNotFoundError였다. 왜냐. 여기에 작성된 스크립트들이 venv로 설정된 하나의 인터프리터를 공유하고 있는 것은 맞아서 pip를 통해 설치한 라이브러리는 공유되지만 어떤 특정 디렉토리에 들어가서 내가 필요한 스크립트를 실행시키면 상위 파일인 constants.py, 혹은 이웃해있는 다른 디렉토리에 작성해놓은 다른 파이썬 파일들을 참조하지 못한다.

 저 구조에서의 예를 들면 collector라는 디렉토리 안에는 api등 여러곳에서 데이터를 가져오는 함수들이 쓰여져 있고 api디렉토리 내부에는 정말 요청만을 위한 함수들이 작성돼있어서 collector라는 디렉토리 내부에 작성된 파이썬 스크립트를 그냥 시키면 api와 collecor를 참조하는 모듈을 찾지 못하고 ModuleNotFoundError를 뱉고 만다. 그럴 땐 아래처럼 필요한 디렉토리를 참조시키는 함수를 작성해주면 된다.

import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

그리고 그 밑에 본인이 직접 작성한 파일들에서 필요한 것들을 불러오면 된다. 함수의 구조를 보면 알게지만 단순히 특정 디렉토리를 스크립트에서 강제로 insert시키는 함수이다.

 

아주 간단한 예로

here_is_outer.py
here_is_inner
└─here_is_inner.py

이런식의 파일구조를 갖고 있는 프로젝트가 있고

# here_is_inner.py
def inner_identification():
    print("This is Inner")
    
# here_is_outer.py
from here_is_inner.here_is_inner import inner_identification


if __name__ == "__main__":
    print("Outer is running...")
    inner_identification()

result

(venv) gyu@gyuui-MacBookPro local_test_python % python here_is_outer.py 
Outer is running...
This is Inner

이런식으로 작성한뒤에 outer 스크립트를 실행시켜보면 inner파일을 참조하는 데 문제가 되지 않는다. 하지만 반대로 inner파일에서 outer파일을 참조하는 스크립트로 실행해보면

# here_is_inner.py
from here_is_outer import outer_identification


if __name__ == "__main__":
    print("Inner is running...")
    outer_identification()

def outer_identification():
    print("This is Outer")

result

Inner is running...
This is Outer

어 왜 되지? 하는 사람은 분명히 IDE에서 루트디렉토리가 설정된 상태로 run을 돌렸을 것이다.

이렇게말이다. 하지만 생짜로 디렉토리에서 스크립트를 실행시켜보면

(venv) gyu@gyuui-MacBookPro here_is_inner % python here_is_inner.py 
Traceback (most recent call last):
  File "/Users/gyu/IdeaProjects/local_test_python/here_is_inner/here_is_inner.py", line 1, in <module>
    from here_is_outer import outer_identification
ModuleNotFoundError: No module named 'here_is_outer'

이렇게 ModuleNotFoundError가 출력되는 것을 확인할 수 있다. 이걸 해결하기 위해 위의 함수를 활용해보자.

# here_is_inner.py
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
# sys.path.insert(0, "/Users/gyu/IdeaProjects/local_test_python/") # 혹은 절대경로

from here_is_outer import outer_identification


def inner_identification():
    print("This is Inner")


if __name__ == "__main__":
    print("Inner is running...")
    outer_identification()

이렇게 작성할 수 있다. 이렇게 작성한 뒤 위 스크립트를 다시 실행시켜보면

(venv) gyu@gyuui-MacBookPro here_is_inner % python here_is_inner.py
Inner is running...
This is Outer

이렇게 잘~ 실행되는 걸 확인할 수 있다. 물론 import와 from구문 사이에 실행되는 함수가 있어서 PEP8 convetion을 지키는 형태가 아니라 flake8이나 black같은 formatter나 linter를 실행시키면 순서가 꼬여서 실행되지 않는 경우가 있으니 해당 이런식으로 작성할 땐 린터 포맷터 사용을 주의하자.

 

나는 이렇게 대충 슥슥 작성한 스크립트를 전에 다뤘던 set_interval함수와 같이 활용해 몇분마다 웹페이지를 크롤링하거나 api response를 데이터베이스에 집어넣는 작업을 하고 있다. 이걸 활용하면 뭔가 서버에 올리긴 귀찮고 그냥 로컬에서 필요할 때만 잠깐 뭐 몇시간이나 하루정도 돌려도 무관한 그런 프로그램들을 작성해서 돌리기 겁나 편해진다. 디렉토리도 깔끔하게 유지할 수 있다.

 

Comments