pip install은 성공했는데 ModuleNotFoundError: No module named가 난 이유
문제 발생
공지사항 페이지를 긁어 오는 짧은 스크립트를 만들었습니다. 검색해 보니 BeautifulSoup를 쓰면 된다고 해서 설치부터 했습니다.
$ pip install beautifulsoup4
Successfully installed beautifulsoup4-4.15.0 soupsieve-...그런데 실행하자마자 이렇게 죽었습니다.
Traceback (most recent call last):
File "/home/me/crawler/crawler.py", line 1, in <module>
from bs4 import BeautifulSoup
ModuleNotFoundError: No module named 'bs4'다시 설치하면 이번엔 Requirement already satisfied가 나옵니다. 설치는 돼 있다는데 Python은 못 찾는 겁니다. 옆에서 보던 동료는 "패키지 이름이 beautifulsoup4니까 import beautifulsoup4로 써야 하는 거 아니냐"고 했고, 그렇게 바꿨더니 No module named 'beautifulsoup4'로 이름만 바뀐 같은 에러가 났습니다. 여기에 VS Code의 실행 버튼으로 돌리면 되고 터미널에서 돌리면 안 되는 날까지 있어서 한동안 헤맸습니다.
원인 분석
pip와 python이 서로 다른 Python이었습니다. 컴퓨터에 Python이 여러 개 깔려 있으면 pip 명령과 python 명령이 각자 다른 Python을 가리킬 수 있습니다. pip 문서는 python -m pip가 "지정한 Python 인터프리터로 pip를 실행한다"고 설명하면서, /usr/bin/python3.7 -m pip는 바로 그 인터프리터의 pip를 실행하는 것이라고 예를 듭니다. 거꾸로 말하면 그냥 pip라고 쳤을 때 어느 Python에 설치될지는 PATH에 먼저 잡힌 pip가 정합니다. 재현 환경에도 Python 3.13과 3.12가 같이 있었는데, 결과는 이랬습니다.
$ pip --version
pip 26.2.1 from /usr/local/lib/python3.13/site-packages/pip (python 3.13)
$ python3 crawler.py
ok
$ python3.12 crawler.py
ModuleNotFoundError: No module named 'bs4'pip는 3.13에 설치했고, 3.12는 자기 site-packages만 뒤지니 못 찾습니다. 두 Python의 sys.path를 찍어 보면 패키지를 찾는 폴더가 아예 다릅니다.
가상환경도 같은 이유로 엇갈립니다. 가상환경은 자기만의 site-packages를 가진 별도의 Python입니다. 바깥 Python에 설치한 bs4는 가상환경 안에서 보이지 않고, 그 반대도 마찬가지입니다. 3.12로 만든 가상환경의 Python으로 같은 스크립트를 돌리자 똑같이 No module named 'bs4'가 났습니다. 터미널마다 활성화 여부가 다르거나, 편집기가 다른 Python을 고르고 있으면 "어디서는 되고 어디서는 안 되는" 상황이 됩니다. VS Code 문서에 따르면 선택한 환경이 코드 실행과 디버깅에 쓰이는데, 이건 터미널에서 직접 친 python과 다를 수 있습니다.
설치하는 이름과 import하는 이름이 다를 수 있습니다. 동료의 의심도 반은 맞았습니다. importlib.metadata 문서는 pip로 설치하는 배포 패키지 이름이 코드에서 import하는 이름과 반드시 1:1로 맞지는 않는다고 적어 둡니다. 하나의 배포 패키지가 여러 import 패키지를 담을 수도 있습니다. beautifulsoup4는 bs4라는 이름으로 import되니 import beautifulsoup4는 처음부터 없는 모듈입니다. 이번엔 이게 원인은 아니었지만, 같은 에러를 내는 흔한 원인입니다.
해결 방안
-
설치는 항상
python -m pip로, 실행할 그 Python으로 합니다.pip install대신python -m pip install beautifulsoup4라고 치면 지금python이 가리키는 바로 그 Python에 설치됩니다. Python 공식 문서도 여러 버전을 같이 쓸 때는 버전 붙은 명령에-m을 붙여python3.14 -m pip install SomePackage처럼 쓰라고 안내하고, Windows에서는py -3.14 -m pip install SomePackage처럼py런처를 쓰라고 합니다. 실행할 때python3.12를 쓸 거라면 설치도python3.12 -m pip로 합니다. -
헷갈리면 두 줄로 지금 어느 Python인지 확인합니다. 아래 두 명령의 경로가 같은 곳을 가리켜야 합니다.
python -c "import sys; print(sys.executable)"
python -m pip --versionpython -m pip show beautifulsoup4도 유용합니다. 재현에서 다른 Python으로 이 명령을 돌리자 WARNING: Package(s) not found: beautifulsoup4가 나왔습니다. "설치했는데 없다"고 나오면 설치한 Python과 지금 Python이 다르다는 신호입니다.
-
편집기와 노트북은 그쪽에서 Python을 맞춥니다. VS Code라면 명령 팔레트에서
Python: Select Interpreter를 실행하거나 상태 표시줄의 Python 버전을 눌러 프로젝트의 가상환경을 고릅니다. Jupyter에서는!pip install대신%pip install을 씁니다. IPython 문서에 따르면%pip는 현재 커널 안에서 pip를 실행합니다.!pip는 셸 명령으로 실행돼 그 셸의PATH에 먼저 잡힌 pip가 불리기 때문에, 커널과 다른 Python에 설치될 수 있습니다. -
import 이름이 헷갈리면 설치된 Python에게 물어봅니다. Python 3.10부터 들어간
importlib.metadata.packages_distributions()는 문서대로 최상위 import 이름을 그걸 제공하는 배포 패키지 이름에 연결해 줍니다. 재현 환경에서 자주 헷갈리는 것들을 찍어 보니 이렇게 나왔습니다.
from importlib.metadata import packages_distributions
m = packages_distributions()
for name in ["bs4", "cv2", "PIL", "yaml", "sklearn", "dotenv", "jwt"]:
print(name, "<-", m.get(name))bs4는 beautifulsoup4, cv2는 opencv-python, PIL은 pillow, yaml은 PyYAML, sklearn은 scikit-learn, dotenv는 python-dotenv, jwt는 PyJWT였습니다. 반대로 설치한 패키지가 어떤 이름으로 import되는지는 python -m pip show -f 패키지명의 파일 목록에서 최상위 폴더 이름을 보면 됩니다.
- 내가 만든 패키지에서 나는 에러는 실행 위치를 봅니다.
app/main.py안에서from app.utils import slug를 하는데python app/main.py로 실행하면No module named 'app'이 납니다. Python 문서의sys.path초기화 설명에 따르면 모듈 검색 경로의 첫 번째 항목은 실행한 스크립트가 들어 있는 디렉터리이고, 스크립트가 없을 때(대화형 셸,-c,-m)는 현재 디렉터리입니다. 그래서python app/main.py는app/폴더 자체를 검색 경로에 넣고, 그 안에는app이라는 패키지가 없습니다. 프로젝트 루트에서python -m app.main으로 실행하니 바로 됐습니다. 루트가 검색 경로에 들어가기 때문입니다. 재현해 보니app/폴더 안으로 들어가서-m으로 돌리면 다시 같은 에러가 났으니, 실행 위치가 루트인지도 함께 확인합니다. 이 경우를sys.path.append로 땜질하기보다 실행 방법을 맞추는 쪽이 오래 갑니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.