[python] 직접 구현한 패키지 import 잘하는법 (ImportError / ModuleNotFoundError 해결법)
파이썬으로 직접 패키지 혹은 모듈을 개발하고 사용하다보면 자주 겪는 에러 중 하나가 Import Error다.
대표적인 예로 아래 두가지 에러가 있다.
[Python] ImportError: attempted relative import with no known parent package
[Python] ModuleNotFoundError: No module named 'module_name'
물론 pip로 패키지를 빌드하고 나서 사용할 때는 패키지만 잘 설치하면 잘 발생하지 않는다.
하지만 웹어플리케이션을 개발할 때 모든 패키지들을 pip로 설치해서 사용하지는 않고 프로젝트 디렉토리에 패키지들을 모아놓고 사용하는 경우도 있는데, 이 때 모듈 내에 import 경로를 잘못 설정하면 위에 말한 에러들을 자주 직면하게 된다. 특히 상대경로로 모듈을 import할 경우에는 혼돈의 카오스를 겪을 수 있다.
따라서 이번 글에서는 해당 에러를 직면하지 않게 패키지 내에 모듈들의 import경로를 어떻게 설정하는게 좋은지 정리하고, 각 상황별로 어떻게 모듈 혹은 main.py를 실행하면 좋을지 정리해보도록 하겠다.
참고로 본 글을 완벽하게 이해하기 위해서는 모듈, 패키지, 라이브러리, 프레임워크의 차이를 다룬 앞 글을 읽고 오는 것을 추천한다.
모듈 import시 경로 확인
일반적으로 우리가 모듈을 실행하게 되면 모듈의 이름은 자동적으로 __main__
이 된다. 뿐만 아니라 다른 모듈들을 import할 때 base경로도 실행되는 모듈이 위치한 경로로 설정된다.
참고로 interpreter가 패키지로 인식하기 위해서는 모든 폴더마다 __init__.py
가 있어야 한다.
이게 무슨 말이냐면 만약
├─ app
│ ├─ util (package)
│ │ ├─ first (package)
│ │ │ └─ first_test.py (module)
│ │ │ └─ __init__.py (constructor for relative)
│ │ ├─ __init__.py(constructor for script)
│ main.py
위와 같은 디렉토리 구조에서 만약 first_test.py
를 아래와 같이 구현하고
#{app 디렉토리 이전 경로}/app/python_module_package_study/app/util/first/first.py
import os
print(os.getcwd())
main.py
에서 아래처럼 first_test.py
를 import하도록 구현한뒤
#{app 디렉토리 이전 경로}/app/python_module_package_study/app/main.py
import script.first.first
main.py
를 실행하면 아래처럼 first_test.py
가 있는 경로가 아닌 main.py
가 위치한 경로가 출력된다.
{app 디렉토리 이전 경로}/app/python_module_package_study/app
위 내용을 이해하고나면 이제 python을 사용해서 package를 만들어두고 사용할때 import error없이 구현하고 사용할 수 있게 된다.
근데 위 예시만으로는 와닿지 않기 때문에 크게 두 가지 상황을 예로 들어서 설명해보도록 하겠다.
1) main.py
하위 경로에 import하려는 패키지가 있는 경우
├─ app
│ ├─ util (package)
│ │ ├─ voice (package)
│ │ │ └─ voice.py (module)
│ │ │ └─ __init__.py (constructor)
│ ├─ __init__.py (constructor)
│ main.py <- 실행하려는 모듈
먼저 voice.py를 아래처럼 구현하고
def person_voice():
print("util.voice.voice : person voice!")
main.py에서 voice를 불러와 person_voice()를 실행해보자
import util.voice.voice as voice
voice.person_voice()
python main.py
#출력
>>> util.voice.voice : person voice!
즉 실행되는 main.py를 기준으로 패키지 경로를 설정해서 voice를 import후 실행해야 한다.
2) main.py
가 import하려는 패키지의 모듈이 동일 경로의 모듈을 import하는 경우
근데 만약에 위 voice 패키지에 voice_tool이라는 모듈을 추가하고, voice에서 이를 사용하게 하려면 어떻게 해야할까?
예를 들어 아래와 같이 voice_tool.py가 새로 구현되고
├─ app
│ ├─ util (package)
│ │ ├─ voice (package)
│ │ │ └─ voice.py (module)
│ │ │ └─ voice_tool.py (module) <- 새로 생긴 모듈
│ │ │ └─ __init__.py (constructor)
│ ├─ __init__.py (constructor)
├─ main.py <- 실행하려는 모듈
#voice_tool.py
def cello_sound():
print("util.voice.voice_tool : cello~")
voice.py에서 voice_tool.py의 cello_sound를 import하고 싶을 때 같은 경로에 있다고 아래처럼 import하고
#voice.py
import voice_tool
def person_voice():
print("util.voice.voice : person voice!")
def cello_sound():
voice_tool.cello_sound()
#main.py
import util.voice.voice as voice
voice.person_voice()
voice.cello_sound()
위처럼 main.py를 구현후 실행하면
ModuleNotFoundError: No module named 'voice_tool'
에러가 발생한다. 이유는 main.py
기준에서 동일 경로에 voice_tool.py
가 없기 때문이다.
따라서 제대로 import되게 하기 위해서는 아래처럼 voice_tool을 import해야 한다.
#voice.py
import util.voice.voice_tool as voice_tool
def person_voice():
print("util.voice.voice : person voice!")
def cello_sound():
voice_tool.cello_sound()
3) main.py
가 import하려는 패키지의 모듈이 상위 경로의 모듈을 import하는 경우
자 그럼 아래처럼 game이라는 새로운 subpackage가 생겼고, 이 game subpackage의 모듈 game.py가 voice패키지의 voice.py를 import하려면 어떻게 해야 할까?
├─ app
│ ├─ util (package)
│ │ ├─ voice (package)
│ │ │ └─ voice.py (module)
│ │ │ └─ voice_tool.py (module)
│ │ │ └─ __init__.py (constructor)
│ │ ├─ game (package)
│ │ │ └─ game.py (module) <- 새로 생긴 모듈
│ │ │ └─ __init__.py (constructor)
│ ├─ __init__.py (constructor)
├─ main.py <- 실행하려는 모듈
2)에서 다뤄봤듯이 실행되는 main.py기준으로 경로를 설정해야 하기 때문에 game.py도
main.py기준으로 voice를 import해야 한다.
import util.voice.voice as voice
def intro():
print("util.game.game : intro called")
voice.person_voice()
이렇게 하고 main.py에서 game을 import해서 실행하면 다음과 같이 출력한다.
import util.voice.voice as voice
import util.game.game as game
voice.person_voice()
voice.cello_sound()
game.intro()
#출력
# util.voice.voice : person voice!
# util.voice.voice_tool : cello~
# util.game.game : intro called
# util.voice.voice : person voice!
4) 실행하려는 모듈이 상위 경로에 있는 모듈을 import하는 경우
자 그러면 이제 좀 독특한 상황을 연출해보자
├─ app
│ ├─ util (package)
│ │ ├─ voice (package)
│ │ │ └─ voice.py (module)
│ │ │ └─ voice_tool.py (module)
│ │ │ └─ __init__.py (constructor)
│ │ ├─ game (package)
│ │ │ └─ game.py (module)
│ │ │ └─ __init__.py (constructor)
│ ├─ __init__.py (constructor)
│ ├─ script (package) <- 새로 생긴 패키지
│ │ ├─ first (package) <- 새로 생긴 sub package
│ │ │ └─ first_script.py (module) <- 실행하려는 모듈
│ │ │ └─ __init__.py (constructor)
├─ main.py
위 상황에서 내가 실행하려는게 first_script.py인데, 이게 util.voice.voice를 import하게 하고 실행하고 싶으면 어떻게 해야할까? 방법은 아래와 같다.
(1) app경로 기준으로 모듈을 import
#{app경로}/app/script/first/first_script.py
import util.voice.voice as voice
(2) app경로에서 -m
옵션 추가해서 실행
-m
는모듈 옵션으로, 패키지 정보가 포함된 형태로 아래처럼 app경로에서 실행해야 한다.
cd {app경로}/app
python -m script.first.first_script
자 이렇게 패키지 활용을 위해 모듈 import경로를 어떻게 해야 할지 살펴보았다.
전체 코드는 아래 레포에 저장해두었으니 참고바란다.
https://github.com/getveryrichet/python_module_package_study
참고로 위 개념을 익히고 나면 상대경로를 사용해서도 패키지를 import할 수 있지만 필자는 추천하지는 않는다. 상위경로의 import는 지원하지 않기 때문에 그냥 명확하게 import경로를 설정하는게 좋다.
무튼 패키지를 맘껏 만들고, 자유자재로 사용해보자~