[Python]

[python] 직접 구현한 패키지 import 잘하는법 (ImportError / ModuleNotFoundError 해결법)

quokkalover 2022. 5. 12. 23:31

 

파이썬으로 직접 패키지 혹은 모듈을 개발하고 사용하다보면 자주 겪는 에러 중 하나가 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

 

GitHub - getveryrichet/python_module_package_study: python_module_package_study

python_module_package_study. Contribute to getveryrichet/python_module_package_study development by creating an account on GitHub.

github.com

 

 

참고로 위 개념을 익히고 나면 상대경로를 사용해서도 패키지를 import할 수 있지만 필자는 추천하지는 않는다. 상위경로의 import는 지원하지 않기 때문에 그냥 명확하게 import경로를 설정하는게 좋다.

 

 

무튼 패키지를 맘껏 만들고, 자유자재로 사용해보자~