설치는 성공했다는데 왜 없다고 할까 — 문제는 에러가 아니라 좌표다
터미널에는 분명히 이렇게 찍혔습니다.
Successfully installed requests-2.32.3그런데 바로 다음 줄에서 import requests가 실패합니다. 이때 가장 흔한 반응이 "설치가 제대로 안 됐나 보다"라며 pip install을 한 번 더 치는 것이고, 그다음이 가상환경 삭제 후 재생성입니다. 운이 좋으면 고쳐지고, 나쁘면 30분 뒤 같은 자리로 돌아옵니다. 재설치는 원인을 없앤 게 아니라 원인을 가린 것뿐이기 때문입니다.
실무에서 이 증상의 원인은 거의 항상 하나로 수렴합니다.
pip이 파일을 써넣은 site-packages 디렉터리와 python이 실제로 읽는 sys.path가 겹치지 않는다.
즉 설치는 정말로 성공했습니다. 다만 다른 파이썬한테 성공했을 뿐입니다. 그래서 이 글은 에러 메시지를 분류하지 않습니다. 에러 문구는 원인을 알려주지 않고, 오히려 여러 원인이 똑같은 문구로 수렴하기 때문입니다. 대신 좌표를 두 개 찍고, 그 둘이 겹치는지만 봅니다.
- 좌표 A: 지금 코드를 실행하는 인터프리터는 누구인가 (
sys.executable) - 좌표 B: 방금 그 pip은 어느 인터프리터에 물려 있는가 (
python -m pip -V,pip -V)
이 두 좌표만 있으면 원인은 6갈래 중 하나로 확정됩니다. 참고로 「파이썬 개발 가이드」 시리즈의 1~7편이 개별 에러 해결이었다면, 이번 8편은 그 위층에 있는 환경 진단 런북입니다. 설치 자체가 정책적으로 막히는 케이스(외부 관리 환경)나 인증서 문제로 다운로드가 실패하는 케이스는 이미 다룬 편이 있으니 거기서 확인하시면 됩니다.
적용 범위: CPython 3.9~3.13, macOS(Homebrew/CLT), Ubuntu 22.04·24.04, Windows 10/11(python.org 설치본), Docker, Jupyter/VS Code.
30초 좌표 찍기: 명령 4개와 판정표
아래 4개를 순서대로, 그대로 붙여 넣으세요. 프롬프트 기호는 없습니다.
which -a python python3 pip pip3python -c "import sys; print(sys.executable); print('---'); print('\n'.join(sys.path))"python -m pip -V
pip -Vpython -c "import sysconfig; print(sysconfig.get_paths()['purelib'])"1) which -a — PATH 위 후보 전부 나열
정상 출력 예시 (venv 활성화 상태, 모든 실행파일이 같은 venv 안)
/home/dev/proj/.venv/bin/python
/usr/bin/python3
/home/dev/proj/.venv/bin/python3
/usr/bin/python3
/home/dev/proj/.venv/bin/pip
/home/dev/proj/.venv/bin/pip3핵심은 첫 줄입니다. which -a는 PATH 우선순위 순으로 나열하므로, 맨 위 항목이 실제로 실행되는 것입니다. python과 pip의 첫 줄이 같은 디렉터리면 정상입니다.
이상 출력 예시
/home/dev/proj/.venv/bin/python
/usr/bin/python3
/usr/local/bin/pip
/home/dev/proj/.venv/bin/pippython은 .venv/bin인데 pip은 /usr/local/bin이 먼저 잡힙니다. 설치는 /usr/local로 나가고 실행은 venv에서 됩니다. 전형적인 불일치입니다.
2) sys.executable + sys.path — 실행 인터프리터와 검색 경로
정상 출력 예시
/home/dev/proj/.venv/bin/python
---
/usr/lib/python3.12
/usr/lib/python3.12/lib-dynload
/home/dev/proj/.venv/lib/python3.12/site-packages마지막 줄에 venv의 site-packages가 들어 있으면 정상입니다.
이상 출력 예시
/usr/bin/python3
---
/usr/lib/python3.12
/usr/lib/python3.12/lib-dynload
/usr/lib/python3/dist-packagesvenv를 활성화했다고 생각했는데 sys.executable이 /usr/bin/python3이고, sys.path 어디에도 .venv가 없습니다. 이 순간 원인은 이미 절반 확정입니다.
3) python -m pip -V vs pip -V — pip의 자기 신고 두 벌
정상 출력 예시
pip 24.2 from /home/dev/proj/.venv/lib/python3.12/site-packages/pip (python 3.12)
pip 24.2 from /home/dev/proj/.venv/lib/python3.12/site-packages/pip (python 3.12)두 줄이 완전히 같습니다.
이상 출력 예시
pip 24.2 from /home/dev/proj/.venv/lib/python3.12/site-packages/pip (python 3.12)
pip 23.0 from /usr/lib/python3/dist-packages/pip (python 3.11)pip이라고 치면 시스템 pip이 실행됩니다. 이 상태에서 pip install을 하면 패키지는 /usr/lib/python3/dist-packages로 갑니다.
4) 설치 목적지 확인 (보너스)
python -c "import sysconfig; print(sysconfig.get_paths()['purelib'])"정상: /home/dev/proj/.venv/lib/python3.12/site-packages
이상: /usr/lib/python3.12/site-packages (venv를 켰다고 생각했는데 시스템 경로)
이미 설치된 패키지가 어디 있는지 확인하려면:
python -m pip show requests | grep -i location정상: Location: /home/dev/proj/.venv/lib/python3.12/site-packages
이상: Location: /home/dev/.local/lib/python3.12/site-packages (--user 영역으로 새어 나간 경우)
두 줄만 보고 판정하는 표
sys.executable(A), python -m pip -V의 괄호·경로(B), pip -V(C) 세 값의 조합입니다. B는 정의상 항상 A에 귀속되므로 실질 변수는 "A와 C가 같은가"와 "A가 내가 의도한 인터프리터인가"입니다.
| A(sys.executable)가 의도한 환경인가 | C(pip -V)가 A와 같은가 | 판정 | 다음 행동 |
|---|---|---|---|
| 예 | 예 | 환경 정합. 문제는 다른 데 있음 | 패키지명·모듈명 불일치(pip install pillow → import PIL), 같은 이름 로컬 파일 그림자 확인 |
| 예 | 아니오 | pip 실행파일이 다른 인터프리터에 바인딩 | python -m pip install 로 재설치 → 3장 ③ |
| 아니오 | 예 | 둘 다 엉뚱한 환경(활성화 실패·shims 미갱신) | 3장 ①·② |
| 아니오 | 아니오 | 완전 분열 상태 | 3장 ①→③ 순으로 정리, 안 되면 5장 venv 재생성 |
여기까지 30초면 끝납니다. 이제 갈래를 확정하러 갑니다.
불일치 6갈래: 식별 신호 → 1줄 복구 → 재발 방지
각 항목은 식별 신호(정상/이상 2벌) → 복구 명령 1줄 → 재발 방지 1줄 고정 포맷입니다.
① 시스템 python과 venv 혼용
식별 신호
python -c "import sys; print(sys.executable); print(sys.prefix != sys.base_prefix)"- 정상:
/home/dev/proj/.venv/bin/python/True - 이상:
/usr/bin/python3/False
sys.prefix != sys.base_prefix가 False면 venv 안이 아닙니다. 프롬프트에 (.venv)가 떠 있어도 그렇습니다. 새 셸을 열었거나, tmux/screen을 새로 띄웠거나, sudo·make·systemd 유닛처럼 환경을 물려받지 않는 컨텍스트에서 실행하면 이렇게 됩니다.
복구 1줄
source /home/dev/proj/.venv/bin/activate && python -c "import sys; print(sys.executable)"재발 방지 1줄: 스크립트·cron·systemd에서는 activate에 의존하지 말고 /home/dev/proj/.venv/bin/python script.py처럼 절대경로 인터프리터를 직접 호출합니다.
② pyenv shims 미갱신
식별 신호
pyenv which python
pyenv version
ls ~/.pyenv/shims | head- 정상:
pyenv which python→/home/dev/.pyenv/versions/3.12.4/bin/python,pyenv version→3.12.4 (set by /home/dev/proj/.python-version) - 이상:
pyenv: python: command not found또는 방금 설치한 CLI가command not found인데pip show로는 설치되어 있음
pyenv는 실행파일 대신 shim(중계 스크립트)을 PATH에 둡니다. 새 패키지가 콘솔 스크립트를 설치하면 shim을 다시 만들어야 인식됩니다.
복구 1줄
pyenv rehash && pyenv which python재발 방지 1줄: 셸 rc에 eval "$(pyenv init -)"가 들어 있는지 확인하고, pyenv 위에서는 전역 설치 대신 프로젝트별 venv(python -m venv .venv)를 기본으로 씁니다.
③ pip 실행파일이 다른 인터프리터에 바인딩
식별 신호
head -1 "$(which pip)"- 정상:
#!/home/dev/proj/.venv/bin/python - 이상:
#!/usr/bin/python3(venv 안인데 셔뱅이 시스템 파이썬)
pip 실행파일의 첫 줄(셔뱅)이 곧 "이 pip이 봉사하는 인터프리터"입니다. venv를 다른 경로로 복사·이동했거나, venv 생성 후 베이스 파이썬을 업그레이드했을 때 자주 깨집니다.
복구 1줄
python -m pip install --force-reinstall pip(그래도 셔뱅이 그대로면 venv 재생성 — 5장 절차)
재발 방지 1줄: 앞으로 pip을 직접 치지 말고 **항상 python -m pip**을 씁니다.
④ --user 설치가 PATH 밖
식별 신호
python -m site --user-site
python -m pip show black | grep -i location
echo "$PATH" | tr ':' '\n' | grep -i local- 정상: 설치한 패키지의 Location이 venv site-packages, 콘솔 스크립트가 바로 실행됨
- 이상:
Location: /home/dev/.local/lib/python3.12/site-packages인데black: command not found
--user(또는 PIP_USER=1 환경변수)로 설치하면 ~/.local에 들어가고, 실행파일은 ~/.local/bin에 놓입니다. 이 경로가 PATH에 없으면 "설치됐는데 실행이 안 되는" 상태가 됩니다. venv 안에서는 --user가 아예 지원되지 않으니, --user가 쓰였다는 건 venv 밖이라는 신호이기도 합니다.
복구 1줄
export PATH="$HOME/.local/bin:$PATH"재발 방지 1줄: pip config list로 user = true 설정이 박혀 있는지 확인하고, CLI 도구는 --user 대신 pipx install, 프로젝트 의존성은 venv로 분리합니다.
⑤ sudo pip으로 root 영역 설치
식별 신호
ls -l /usr/local/lib/python3.12/site-packages | head
python -m pip show requests | grep -i location- 정상: 프로젝트 패키지들이 venv 아래에만 존재, 소유자는 일반 유저
- 이상:
/usr/local/lib/python3.*/site-packages아래 파일 소유자가root, 일반 유저로pip install --upgrade시 Permission denied
sudo pip은 시스템 패키지 관리자(apt/dnf/brew)가 관리하는 파일을 덮어써서 OS 도구를 망가뜨릴 수 있습니다. 정리 순서는 이렇습니다.
sudo python3 -m pip list --format=freeze > /tmp/root-pip.txt로 현황 기록- 프로젝트에 필요한 것만 골라 venv에 재설치 (
python -m pip install -r requirements.txt) - 애플리케이션이 정상 동작하는지 검증
- 그다음에야 root 영역에서 제거 (
sudo python3 -m pip uninstall <pkg>) — apt가 넣은 패키지는 건드리지 말 것
복구 1줄
python -m venv .venv && source .venv/bin/activate && python -m pip install -r requirements.txt재발 방지 1줄: sudo pip / sudo pip3 문자열을 pre-commit 훅이나 셸 alias로 막아 둡니다(5장 참조).
⑥ IDE·Jupyter 커널이 다른 인터프리터 선택
식별 신호 — 터미널이 아니라 에디터/노트북 안에서 실행합니다.
import sys; print(sys.executable)- 정상:
/home/dev/proj/.venv/bin/python(터미널 결과와 동일) - 이상:
/usr/bin/python3또는/opt/homebrew/bin/python3.11(터미널과 다름)
VS Code Python 확장의 자동 감지가 좋아졌지만, 워크스페이스를 상위 폴더로 열었거나 venv를 나중에 만들면 여전히 예전 인터프리터를 물고 있습니다.
복구 1줄: VS Code에서 Ctrl/Cmd+Shift+P → Python: Select Interpreter → .venv/bin/python 선택 후 터미널과 커널 모두 재시작 (PyCharm은 Settings → Project → Python Interpreter에서 동일 경로 지정)
재발 방지 1줄: 프로젝트 루트에 .vscode/settings.json을 커밋해 인터프리터를 고정합니다.
{
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.terminal.activateEnvironment": true
}환경별 차이와 Jupyter 전용 절
macOS (Homebrew + Command Line Tools 공존)
which -a python3
/opt/homebrew/bin/python3 -V
/usr/bin/python3 -V
brew list --versions [email protected]- 정상:
/opt/homebrew/bin/python3가 먼저 잡히고 버전이 의도한 값 - 이상:
/usr/bin/python3(Apple 제공 CLT 파이썬)가 먼저 잡힘 →.zshrc의 PATH 순서 점검
Apple 제공 /usr/bin/python3는 OS 도구용이라 여기에 패키지를 설치하는 습관은 피하는 게 좋습니다. 최신 macOS와 Homebrew에서 시스템/관리 환경 보호가 강화되면서 설치 자체가 차단되는 케이스는 PEP 668 externally-managed-environment 편에서 다뤘습니다.
Ubuntu 24.04
python3 -c "import sys; print(sys.path)"
dpkg -S /usr/lib/python3/dist-packages/yaml 2>/dev/null
apt list --installed 2>/dev/null | grep python3-Debian 계열은 apt로 설치한 패키지를 /usr/lib/python3/dist-packages에, pip으로 설치한 것을 site-packages 또는 ~/.local에 둡니다. 경로가 아예 다릅니다. 그래서 apt install python3-requests로 설치해 놓고 venv 안에서 import requests를 하면 없다고 나옵니다(venv는 기본적으로 시스템 패키지를 상속하지 않음).
- 정상: 프로젝트 의존성은 전부 venv site-packages 한 곳
- 이상: 일부는 dist-packages, 일부는 venv → 버전 충돌의 씨앗
시스템 파이썬을 건드리지 말고 항상 venv를 만드세요.
sudo apt install -y python3-venv
python3 -m venv .venv && source .venv/bin/activate && python -m pip install -U pipWindows (PowerShell)
Windows에는 런처(py) 라는 강력한 도구가 있습니다.
py -0p정상 출력 예시
-V:3.12 * C:\Users\dev\AppData\Local\Programs\Python\Python312\python.exe
-V:3.11 C:\Users\dev\AppData\Local\Programs\Python\Python311\python.exe*가 붙은 게 기본값입니다. 여러 버전이 깔려 있고 어디에 설치됐는지 헷갈릴 때, 설치 대상을 버전으로 고정해 버립니다.
py -3.12 -m pip install requests
py -3.12 -c "import requests; print(requests.__file__)"venv를 쓸 때는:
py -3.12 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -c "import sys; print(sys.executable)"- 정상:
C:\proj\.venv\Scripts\python.exe - 이상:
C:\Users\dev\AppData\Local\Microsoft\WindowsApps\python.exe→ Microsoft Store 스텁이 잡힌 것.설정 → 앱 → 앱 실행 별칭에서 Python 별칭을 끄세요.
Docker (멀티스테이지에서 venv COPY)
venv 안의 스크립트 셔뱅에는 생성 당시의 절대경로가 박힙니다. 빌더 스테이지에서 /opt/venv로 만든 걸 런타임 스테이지의 다른 경로로 COPY하면 셔뱅이 깨집니다. 경로를 양쪽에서 동일하게 유지하는 게 핵심입니다.
FROM python:3.12-slim AS builder
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN python -m pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
COPY . .
CMD ["python", "-m", "app"]검증:
docker run --rm myimage python -c "import sys; print(sys.executable); print(sys.prefix != sys.base_prefix)"- 정상:
/opt/venv/bin/python/True - 이상:
/usr/local/bin/python/False→ENV PATH누락 또는 COPY 경로 불일치
Jupyter: !pip과 %pip은 전혀 다릅니다
노트북에서 가장 흔한 함정입니다.
| 명령 | 실행 주체 | 설치 대상 |
|---|---|---|
!pip install X | 서브셸 → 셸 PATH의 pip | 커널과 무관한 인터프리터일 수 있음 |
%pip install X | IPython 매직 → 현재 커널 인터프리터의 pip | 커널과 항상 일치 |
!{sys.executable} -m pip install X | 커널 인터프리터 직접 지정 | 커널과 항상 일치 |
즉 !pip install로 설치하고 import가 실패했다면 그건 버그가 아니라 설계대로 동작한 결과입니다. 노트북에서는 %pip을 쓰세요.
커널이 어느 인터프리터를 쓰는지 직접 확인하려면:
jupyter kernelspec list
cat ~/.local/share/jupyter/kernels/proj-venv/kernel.json정상 출력 예시
{
"argv": ["/home/dev/proj/.venv/bin/python", "-m", "ipykernel_launcher", "-f", "{connection_file}"],
"display_name": "Python (proj-venv)",
"language": "python"
}이상 출력 예시
{
"argv": ["/usr/bin/python3", "-m", "ipykernel_launcher", "-f", "{connection_file}"],
"display_name": "Python 3 (ipykernel)",
"language": "python"
}argv[0]이 내 venv가 아니면 커널을 다시 등록합니다.
source .venv/bin/activate
python -m pip install ipykernel
python -m ipykernel install --user --name proj-venv --display-name "Python (proj-venv)"등록 후 노트북에서 커널을 Python (proj-venv)로 바꾸고 재시작한 뒤, import sys; print(sys.executable)로 검증하면 끝입니다.
왜 python -m pip이 정답인가 (원리 한 문단)
pip은 PATH 어딘가에 놓인 별개의 실행파일이라 어느 인터프리터에 물려 있는지 셔뱅을 열어보기 전엔 알 수 없습니다. 반면 -m은 "지금 실행 중인 바로 이 인터프리터가 자기 sys.path에서 pip 모듈을 찾아 실행하라"는 뜻입니다. 실행 주체가 곧 설치 대상이 되므로, 좌표 A와 좌표 B가 정의상 항상 일치합니다. 이 한 줄이 6갈래 중 ②③④를 구조적으로 제거합니다. 최근 uv·pipx가 빠르게 확산되는 것도 결국 "인터프리터와 설치 대상을 강제로 묶어 두자"는 같은 철학의 연장선입니다.
팀 표준화 체크리스트와 최후의 수단
팀에 박아 둘 4가지
- venv 경로 고정: 프로젝트 루트의
.venv한 곳만 사용..gitignore에.venv/추가, README 첫 줄에 생성 명령 명시 python -m pip강제: CI 스크립트·Makefile·문서의 모든pip install을python -m pip install로 치환- 버전 핀:
requirements.txt는==로 고정하고python -m pip freeze > requirements.lock.txt를 산출물로 관리 sudo pip차단: 아래 훅으로 커밋 단계에서 막습니다
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: no-sudo-pip
name: block sudo pip in scripts
entry: bash -c 'grep -rInE "sudo +pip3?" --include="*.sh" --include="Makefile" --include="*.md" . && exit 1 || exit 0'
language: system
pass_filenames: false최후의 수단: venv 재생성 (삭제 말고 이름 변경부터)
진단해도 안 잡히면 재생성합니다. 다만 지우기 전에 백업하는 순서가 중요합니다.
# 1) 현재 상태 백업
python -m pip freeze > requirements.lock.txt
# 2) 삭제가 아니라 이름 변경 (롤백 가능하게)
mv .venv .venv.bak
# 3) 새 venv 생성
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip setuptools wheel
# 4) 복원
python -m pip install -r requirements.lock.txt
# 5) 검증
python -c "import sys; print(sys.executable); print(sys.prefix != sys.base_prefix)"
python -m pip -V
head -1 "$(which pip)"
# 6) 문제없으면 구 venv 제거
rm -rf .venv.bak5번에서 sys.executable이 새 .venv/bin/python, 두 번째 값이 True, pip 셔뱅이 같은 경로면 완료입니다. 만약 4번에서 특정 패키지 설치가 실패한다면 그건 경로 문제가 아니라 의존성 해석 문제이니 별도 축으로 접근해야 합니다. 다운로드 단계에서 인증서 오류가 난다면 SSLCertVerificationError 편을 참고하세요.
한 장 요약 카드
1. python -c "import sys; print(sys.executable)"
2. python -m pip -V / pip -V
3. 두 값이 다르면 → python -m pip 으로 재설치
4. sys.executable이 의도한 venv가 아니면 → 활성화/커널/셔뱅 점검
5. 의심되면 무조건 python -m pip다음 편(파이썬 개발 가이드 9편)에서는 경로가 아니라 버전이 문제인 경우 — ResolutionImpossible로 대표되는 의존성 충돌과 핀 전략을 다룹니다.
자주 묻는 질문 (FAQ)
Q. pip list에는 분명히 보이는데 import만 실패합니다. 왜죠?
A. pip list를 보여준 pip과 import를 시도한 python이 서로 다른 인터프리터일 가능성이 가장 큽니다. python -m pip list로 다시 확인해 보세요. 목록에서 사라진다면 확정입니다. 두 경우 모두 보인다면 패키지명과 모듈명이 다른 케이스(pip install pillow → import PIL, pip install beautifulsoup4 → import bs4)이거나, 작업 디렉터리에 같은 이름의 .py 파일이 있어 그림자가 생긴 경우를 의심하세요.
Q. 주피터 노트북에서 !pip install을 했는데 왜 import가 안 되나요?
A. !pip은 서브셸을 띄워 셸 PATH의 pip을 실행하므로 노트북 커널이 쓰는 인터프리터와 무관할 수 있습니다. %pip install(IPython 매직)을 쓰면 현재 커널 인터프리터에 설치됩니다. 이미 커널 자체가 엉뚱한 인터프리터를 물고 있다면 venv를 활성화한 상태에서 python -m ipykernel install --user --name proj-venv로 커널을 다시 등록하고 kernel.json의 argv[0]을 확인하세요.
Q. 매번 venv를 만들기 귀찮은데 --user 설치는 왜 권장되지 않나요?
A. ~/.local은 모든 프로젝트가 공유하는 공간이라 프로젝트 A가 필요로 하는 버전과 B가 필요로 하는 버전이 충돌합니다. 또 실행파일이 놓이는 ~/.local/bin이 PATH에 없으면 "설치는 됐는데 명령을 못 찾는" 상태가 됩니다. 여러 프로젝트에서 공통으로 쓰는 CLI 도구는 pipx로, 프로젝트 의존성은 venv(또는 uv)로 분리하는 것이 유지보수 비용이 가장 낮습니다.
AI 도구는 자료 조사와 초안 작성의 보조 수단으로 사용될 수 있습니다. Nodelog는 공개 전 내용과 출처를 검토하고, 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서를 함께 확인하도록 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
댓글
첫 번째 댓글을 남겨보세요.