pip 설치 오류 externally-managed-environment, venv와 pipx 선택

pip는 설치를 막고, 대신 쓴 pipx는 설치를 마쳤는데도 import가 안 된다. externally-managed-environment 오류를 피한 뒤에는 왜 또 모듈을 찾지 못할까?

pip 설치 오류 externally-managed-environment, venv와 pipx 선택
세 줄 요약
  • externally-managed-environment는 배포판이나 Homebrew가 관리하는 Python의 기본 설치 영역을 보호하는 오류다.
  • 스크립트가 import할 패키지는 프로젝트의 venv에, 터미널에서 독립적으로 실행할 앱은 pipx에 설치한다.
  • 설치와 실행에 같은 Python 경로를 쓰면 패키지가 다른 환경에 들어가는 혼동을 줄일 수 있다.

라이브러리를 쓸 때와 명령을 실행할 때

Ubuntu나 Homebrew의 Python에서 pip install을 실행했는데 externally-managed-environment가 나오면, 설치할 패키지를 어디에서 쓸지에 따라 해결 경로가 달라진다. 내가 작성한 코드에서 import requests로 불러올 라이브러리는 프로젝트 가상환경에 설치한다. 반면 터미널에서 black 같은 명령을 실행하려는 목적이면 pipx로 앱을 설치할 수 있다. 두 방법 모두 프로젝트나 앱의 패키지를 기본 Python 설치 영역과 분리한다.

pipx로 설치한 뒤에도 import가 실패하는 사례가 이 차이를 보여준다. 한 Ubuntu 이용자는 Reddit에서 ib_async를 pipx에 설치해 목록에 보이는데도 자신의 스크립트에서는 모듈을 찾지 못한다고 질문했다. 이후 가상환경을 다시 설정하자 작동했다고 답했다. pipx가 만든 앱 환경에 패키지가 존재해도, 다른 Python으로 실행한 스크립트가 그 환경의 라이브러리를 읽는 것은 아니다.

하려는 작업설치할 곳실행 방식
내 코드에서 라이브러리를 불러온다.해당 프로젝트의 venv에 설치한다.그 venv의 Python으로 코드를 실행한다.
독립된 Python 명령줄 앱을 쓴다.pipx가 관리하는 앱 환경에 설치한다.PATH에 연결된 앱 명령을 실행한다.
운영체제가 제공하는 Python 구성요소를 설치한다.배포판 패키지 관리자로 설치한다.배포판이 정한 Python과 패키지 구성을 유지한다.

선택은 패키지 이름만으로 정해지지 않는다. 예를 들어 코드 포매터를 여러 프로젝트에서 사람이 직접 실행하려면 pipx가 편하다. 팀 프로젝트의 검사 명령을 같은 의존성 목록으로 설치해야 한다면 그 프로젝트의 가상환경에 넣을 수도 있다. 이때는 프로젝트에서 정한 설치 파일과 실행 절차를 따른다. 기존 프로젝트가 이미 환경 관리 도구를 사용한다면 별도의 설치 방식을 덧붙이기보다 그 환경에 필요한 의존성을 추가하는 편이 실행 대상을 맞추기 쉽다.

프로젝트 안에 설치하고 같은 Python으로 실행하기

아래 명령은 Ubuntu 또는 Homebrew Python이 있는 macOS의 bash·zsh 터미널을 기준으로 한다. 새 연습 폴더에서 requests를 설치하는 예시다. 이미 작업 중인 프로젝트라면 그 폴더로 이동하고, 기존 .venv가 있는지부터 살핀다. 같은 이름의 환경을 덮어 만들거나 지울 필요는 없다.

Ubuntu에서 venv 모듈이 없다는 안내나 가상환경 생성 중 ensurepip 관련 안내가 나오면, Ubuntu가 제공하는 python3-full을 설치해 가상환경 도구를 준비할 수 있다. 다음 명령은 Ubuntu용이며 macOS에서는 실행하지 않는다. 관리자 권한은 배포판 패키지를 설치하는 이 단계에 사용한다.

sudo apt update
sudo apt install python3-full

이제 아직 존재하지 않는 폴더 이름으로 연습 프로젝트를 만든다. 첫 명령에서 이미 존재하는 폴더라는 메시지가 나오면 다른 이름을 쓰거나 해당 프로젝트의 기존 환경을 사용한다.

mkdir python-demo
cd python-demo
python3 -m venv .venv
.venv/bin/python -m pip install requests
.venv/bin/python -c "import requests; print(requests.__file__)"

venv는 지정한 폴더에 Python 실행 경로와 독립된 패키지 설치 영역을 만든다. 위 예시의 .venv/bin/python은 그 환경의 Python이다. 이어 붙인 -m pip는 그 Python에 속한 pip를 실행하므로 requests가 같은 환경에 설치된다. 마지막 명령은 실제로 import한 requests의 파일 경로를 출력한다. 설치가 정상적으로 끝났다면 프로젝트의 .venv 아래 패키지 위치가 보인다.

가상환경 생성 명령이 실패했다면 그다음 설치 명령으로 넘어가지 않는다. 생성 오류를 해결한 뒤 환경의 Python이 생겨야 같은 경로를 사용할 수 있다. Ubuntu에서 모듈을 준비한 뒤에도 실패한다면 표시된 권한·디스크·경로 오류를 따라 해결한다. Homebrew를 쓰는 Mac에 Ubuntu용 패키지 이름을 적용하거나 시스템 Python을 제거하는 방식으로 고치지 않는다.

작업 스크립트도 같은 Python으로 실행한다. 프로젝트 폴더에 이미 app.py를 작성해 둔 경우 실행 명령은 다음과 같다.

.venv/bin/python app.py

소스 코드와 입력 자료는 python-demo 같은 프로젝트 폴더에 두고, .venv 안에는 넣지 않는다. 가상환경은 의존성을 다시 설치해 재생성할 수 있지만 직접 작성한 코드와 데이터는 그렇게 복구되지 않는다. 환경 폴더와 작업 파일을 나눠 두면 Python을 바꿀 때도 원본 작업을 남긴 채 새 환경을 만들 수 있다.

설치했는데 import가 안 되는 경우

명령을 짧게 쓰고 싶으면 가상환경을 활성화한다. bash·zsh에서는 다음과 같이 실행한다. 활성화는 현재 셸의 PATH 앞쪽에 환경의 실행 폴더를 넣어, 짧은 python 명령이 그 환경을 가리키도록 한다.

source .venv/bin/activate
python -m pip show requests
python app.py
deactivate

활성화는 필수 절차가 아니다. 앞 절처럼 .venv/bin/python을 직접 지정하면 활성화하지 않고도 같은 환경을 사용한다. 반대로 터미널을 새로 열거나 다른 실행 도구에서 스크립트를 시작하면 이전 셸의 활성화가 그대로 적용된다고 볼 수 없다. 편집기에서만 import가 실패한다면 편집기의 Python 선택 항목에서 이 프로젝트의 .venv/bin/python을 지정한다.

현재 셸의 python3가 어느 환경인지 알고 싶으면 실행 파일과 가상환경 여부를 함께 출력한다. 이어지는 pip 조회도 같은 명령 이름에 붙인다.

python3 -c "import sys; print(sys.executable); print(sys.prefix != sys.base_prefix)"
python3 -m pip show requests

첫 출력은 실행 중인 Python의 경로다. 둘째 출력이 False면 가상환경 밖에서 실행 중이며, True여도 다른 프로젝트의 환경일 수 있으므로 첫 경로를 함께 본다. Python 문서는 prefix와 base_prefix가 다른지로 가상환경 실행 여부를 판별한다. 요청한 패키지를 찾지 못했다는 pip 결과까지 나오면, 같은 작업을 .venv/bin/python으로 다시 조회해 설치 대상과 실행 대상을 맞춘다.

pip show의 Location에는 조회한 패키지의 설치 위치가 나온다. pipx 목록에 보이는 앱 환경과 이 위치는 서로 다를 수 있다. pipx 환경의 내부 폴더를 억지로 스크립트 검색 경로에 추가하면 앱의 의존성 변경이 프로젝트에도 영향을 준다. 프로젝트 Python에 필요한 라이브러리를 설치하면 프로젝트가 자신의 의존성을 관리하게 된다.

관리되는 Python에서 pip가 설치를 막는 이유

Ubuntu의 기본 Python이나 Homebrew가 설치한 Python은 해당 패키지 관리자가 공급하고 업데이트한다. 여기에 pip가 별도의 버전으로 라이브러리를 설치하면, 배포판이 기대한 라이브러리 대신 새 패키지가 먼저 불릴 수 있다. 파일을 직접 덮어쓰지 않더라도 Python이 검색하는 경로 순서에 따라 다른 버전이 선택될 수 있는 것이다. PEP 668은 이런 충돌을 시스템 전체 설치와 사용자 폴더 설치 양쪽의 문제로 설명한다.

현재 패키징 규격은 가상환경 밖에서 실행 중이고 공급자가 관리 표시를 둔 Python이면 설치 도구가 기본 설치를 중단하도록 정한다. 그 표시가 EXTERNALLY-MANAGED다. 따라서 같은 Python 버전이라고 모든 컴퓨터에서 이 오류가 나는 것은 아니다. Python을 누가 어떤 방식으로 제공했는지가 달라 결과도 달라진다.

--user는 설치 장소를 사용자 영역으로 바꾸지만, 기본 Python이 그 패키지를 읽을 수 있는 문제까지 없애지는 않는다. 이 때문에 관리되는 환경에서는 이 옵션으로도 설치가 막힐 수 있다. --break-system-packages는 차단을 우회하는 옵션이며 프로젝트 환경을 만들어 주지 않는다. 일반 프로젝트의 라이브러리를 설치할 때는 위의 venv 절차로 설치 영역을 나누면 된다.

Homebrew도 프로젝트는 venv, 독립 앱은 pipx로 관리하는 방식을 안내한다. Homebrew가 관리하는 기본 Python의 패키지를 pip로 강제 교체하면 두 관리 도구가 서로 다른 설치 기록을 보게 된다. 기본 Python 업데이트는 Homebrew의 관리 절차에 맡기고, 내 프로젝트의 라이브러리는 프로젝트 환경에서 갱신하는 구성이 맞다.

명령줄 앱을 설치하고 관리하는 pipx

pipx는 앱마다 격리된 환경을 만들고 앱의 명령을 PATH에서 실행할 수 있게 연결한다. 그래서 여러 프로젝트 밖에서 같은 명령줄 도구를 부르기 편하다. 이 연결은 실행 명령을 제공하는 기능이다. 일반 Python 스크립트의 import 경로에 앱의 라이브러리를 공개하는 기능은 아니다.

pipx 설치 문서에 따라 Homebrew를 쓰는 Mac에서는 다음 명령으로 pipx를 준비한다.

brew install pipx
pipx ensurepath

Ubuntu 23.04 이상에서 배포판 패키지를 쓰는 경로는 다음과 같다. 위의 Mac 명령과 아래 명령은 운영체제별 대안이며 둘 다 실행하는 순서가 아니다.

sudo apt update
sudo apt install pipx
pipx ensurepath

PATH 변경이 적용되도록 터미널을 새로 연 뒤 예시 앱 Black을 설치한다. 버전 조회는 명령을 찾을 수 있는지 확인하는 동작이며 프로젝트 코드를 포맷하지 않는다.

pipx install black
black --version
pipx list

앱을 갱신하려면 pipx upgrade black, 사용을 끝내 제거하려면 pipx uninstall black을 쓴다. 기본 Python을 바꾼 뒤 해당 앱 환경을 다시 구성할 필요가 있으면 pipx reinstall black을 사용할 수 있다. 이 명령들은 pipx가 관리하는 앱 환경을 대상으로 한다. 프로젝트의 requests를 갱신하려는 경우에는 그 프로젝트 Python의 pip를 사용한다.

프로젝트를 옮길 때 남길 파일

프로젝트 폴더를 다른 위치나 컴퓨터로 옮길 때는 소스와 데이터를 옮기고 가상환경을 새로 만드는 방식이 맞다. venv 안의 실행 스크립트에는 인터프리터의 절대 경로가 들어갈 수 있어 폴더를 복사했다고 새 위치에서 그대로 동작하지 않는다. .venv는 설치된 결과이며, 재설치할 패키지 목록과 작업 원본을 대신하지 않는다.

프로젝트가 이미 제공하는 의존성 파일이 있으면 그 설치 절차를 따른다. 간단한 개인 프로젝트에서 현재 환경의 설치 목록을 따로 남기려면 다음처럼 새 파일명으로 저장할 수 있다. 같은 이름의 파일이 있으면 다른 이름을 고른다. 셸의 >는 기존 파일 내용을 덮어쓴다.

.venv/bin/python -m pip freeze > requirements.snapshot.txt

pip freeze는 설치된 패키지 목록을 requirements 형식으로 출력한다. 의존성을 새로 해결하는 도구나 잠금 파일 생성기는 아니다. Python 버전과 운영체제가 달라지면 동일 목록의 설치 가능성도 달라질 수 있다. 목록과 함께 사용한 Python 버전을 기록해 두면 새 환경을 준비할 때 비교할 수 있다.

새 위치에서 python3 -m venv .venv로 환경을 만든 뒤 .venv/bin/python -m pip install -r requirements.snapshot.txt로 목록을 설치한다. 마지막으로 새 환경의 Python으로 실제 작업 스크립트를 실행한다. 코드와 입력 데이터가 이전 위치에 남아 있거나 외부 서비스 설정이 빠져 있다면 패키지 설치만으로 작업이 완성되지는 않는다.