Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 668 – Python 기본 환경을 “외부에서 관리됨”으로 표시하기

Author:
Geoffrey Thomas <geofft at ldpreload.com>, Matthias Klose <doko at ubuntu.com>, Filipe Laíns <lains at python.org>, Donald Stufft <donald at stufft.io>, Tzu-ping Chung <uranusjr at gmail.com>, Stefano Rivera <stefanor at debian.org>, Elana Hashman <ehashman at debian.org>, Pradyun Gedam <pradyunsg at gmail.com>
PEP-Delegate:
Paul Moore <p.f.moore at gmail.com>
Discussions-To:
Discourse thread
Status:
Final
Type:
Standards Track
Topic:
Packaging
Created:
18-May-2021
Post-History:
28-May-2021
Resolution:
Discourse message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP is a historical document. The up-to-date, canonical spec, Externally Managed Environments, is maintained on the PyPA specs page.

×

See the PyPA specification update process for how to propose changes.

초록

Python 사용자들이 오랫동안 겪어 온 실질적인 문제는 OS 패키지 관리자와 pip 같은 Python 전용 패키지 관리 도구 사이의 충돌입니다. 이러한 충돌에는 Python 수준의 API 비호환성과 파일 소유권을 둘러싼 충돌이 모두 포함됩니다.

역사적으로 Python 전용 패키지 관리 도구는 기본적으로 암묵적인 전역 컨텍스트에 패키지를 설치해 왔습니다. 가상 환경이 표준화되고 널리 쓰이게 되면서, 대부분의(전부는 아닌) 사용 사례에서 더 나은 해결책은 Python 전용 패키지 관리 도구를 가상 환경 안에서만 사용하는 것입니다.

이 PEP는 Python 설치본이 자신의 전역 패키지 설치 컨텍스트가 OS 패키지 관리자처럼 Python 외부의 수단으로 관리된다는 사실을 pip 같은 도구에 알릴 수 있는 메커니즘을 제안합니다. 이 PEP는 Python 전용 패키지 관리 도구가 기본적으로 인터프리터의 전역 컨텍스트에 패키지를 설치하거나 제거하지 말고, 대신 최종 사용자가 가상 환경을 사용하도록 안내해야 한다고 규정합니다.

또한 sysconfig 스킴의 해석을 표준화하여, Python 전용 패키지 관리자가 인터프리터 전체에 적용되는 컨텍스트에 패키지를 설치하려 할 때 외부 패키지 관리자와의 충돌을 피하고 외부 패키지 관리자가 제공하는 소프트웨어를 망가뜨릴 위험을 줄이는 방식으로 설치할 수 있도록 합니다.

용어

이 PEP에서 사용하는 몇몇 용어는 이 PEP가 걸쳐 있는 여러 맥락에서 각기 다른 의미를 가집니다. 명확성을 위해 이 PEP는 다음 용어를 특정한 방식으로 사용합니다:

배포판
“distribution(배포)”의 줄임말로, 이상적으로는 서로 잘 맞물려 동작하도록 설계된 다양한 종류의 소프트웨어 모음입니다. 여기에는 (이 문서와 관련된 맥락에서) Python 인터프리터 자체, Python으로 작성된 소프트웨어, 다른 언어로 작성된 소프트웨어가 포함됩니다. 즉, “Linux 배포판”이나 “Berkeley Software Distribution” 같은 표현에서 쓰이는 의미입니다.

배포판은 Debian, Fedora, FreeBSD처럼 그 자체로 하나의 운영 체제(OS)일 수 있습니다. 또한 Homebrew나 MacPorts처럼 기존 OS 위에 설치되는 오버레이 배포판일 수도 있습니다.

이 문서는 “distro(배포판)”라는 짧은 용어를 사용합니다. “distribution(배포)”이라는 용어는 Python 패키징 맥락에서 다른 의미, 즉 Python 언어로 작성된 단일 소프트웨어의 소스 또는 바이너리 배포 패키지라는 의미를 갖기 때문입니다. 이는 setuptools.dist.Distribution이나 “sdist”에서 쓰이는 의미입니다. 혼동을 피하기 위해 이 문서는 “배포(distribution)”라는 단독 용어를 전혀 사용하지 않습니다. Python 패키징의 의미로는 “배포 패키지”라는 전체 표현이나 그냥 “패키지”라는 표현을 사용합니다(아래 참조).

배포판의 제공자, 즉 소프트웨어를 수집하여 배포하고 필요한 수정을 가하는 팀이나 회사는 그 배포판의 배포자입니다.

패키지
Python 안에서 설치하고 사용할 수 있는 소프트웨어 단위입니다. 즉, 이는 Python 전용 패키징 도구들이 흔히 “distribution package” 또는 간단히 “distribution(배포)”이라고 부르는 것을 가리킵니다. 구어적 약칭인 “package”는 Python Package Index(Python 패키지 색인)에서 쓰이는 의미로 사용됩니다.

많은 경우 배포 패키지가 같은 이름의 임포트 가능한 패키지 하나로 이루어지기는 하지만, 이 문서에서는 Python 모듈을 담고 있는 임포트 가능한 이름이라는 의미로 “패키지”를 사용하지 않습니다.

이 문서에서는 일반적으로 배포판의 패키지 관리자가 설치하는 단위(.deb나 .rpm 파일 등)를 가리키는 데 “패키지”라는 용어를 사용하지 않습니다. 필요한 경우에는 “배포판의 패키지” 같은 표현을 사용합니다. (마찬가지로, 많은 경우 Python 패키지는 python- 뒤에 Python 패키지 이름을 붙인 것과 비슷한 이름의 배포판 패키지 안에 담겨 제공됩니다.)

Python 전용 패키지 관리자
Python 패키징 표준(PEP 376과 PEP 427 등)을 준수하는 방식으로 Python 패키지를 설치, 업그레이드, 제거하는 도구입니다. 가장 널리 쓰이는 Python 전용 패키지 관리자는 pip [1]이며, 그 밖의 예로는 예전의 Easy Install 명령 [2]과 setup.py 명령을 직접 사용하는 방식이 있습니다.

(Conda [3]는 약간 특수한 경우로, conda 명령은 Python 패키지보다 훨씬 많은 것을 설치할 수 있어서 어떤 면에서는 배포판 패키지 관리자에 더 가깝습니다. conda 명령은 일반적으로 Conda가 만든 환경에서만 동작하므로, conda가 Python 전용 패키지 관리자로 동작할 때에는 이 문서의 우려 사항 대부분이 해당하지 않습니다.)

배포판 패키지 관리자
배포판이 설치된 인스턴스에서 해당 배포판의 패키지를 설치, 업그레이드, 제거하는 도구입니다. 이 도구는 Python 패키지뿐만 아니라 Python이 아닌 패키지도 설치할 수 있으므로, 일반적으로 PEP 376과 무관한 자체 설치 소프트웨어 데이터베이스를 가지고 있습니다. 예로는 apt, dpkg, dnf, rpm, pacman, brew가 있습니다. 두드러진 특징은, 배포판 패키지 관리자가 설치한 패키지를 Python 전용 패키지 관리자가 원하는 방식으로 제거하거나 업그레이드하면 일반적으로 배포판 패키지 관리자가 일관성 없는 상태에 놓이게 된다는 점입니다.

이 문서에서는 특정 맥락에서 배포판 패키지 관리자를 가리키기 위해 “외부 패키지 관리자”나 “시스템 패키지 관리자” 같은 표현도 사용합니다.

섀도잉(shadow)
설치된 Python 패키지를 섀도잉한다는 것은, 섀도잉되는 패키지의 파일을 전혀 제거하지 않으면서 임포트 시 다른 패키지가 우선적으로 선택되도록 하는 것을 말합니다. 이를 위해서는 sys.path에 여러 항목이 있어야 합니다. 패키지 A 2.0이 한 sys.path 항목에 모듈 a.py를 설치하고, 패키지 A 1.0이 그보다 뒤에 있는 sys.path 항목에 모듈 a.py를 설치하면, import a는 앞쪽 항목의 모듈을 반환하며, 이때 A 2.0이 A 1.0을 섀도잉한다고 말합니다.

동기

Python의 엄청난 인기 덕분에, 소프트웨어 배포판(여기서는 Linux 및 기타 OS 배포판뿐만 아니라 Homebrew나 MacPorts 같은 오버레이 배포판도 포함합니다)은 일반적으로 두 가지 목적으로 Python을 제공합니다. 하나는 최종 사용자가 그 자체로 사용하는 소프트웨어 패키지로서이고, 다른 하나는 배포판 내 다른 소프트웨어를 위한 언어 의존성으로서입니다.

예를 들어, Fedora와 Debian(그리고 그 파생 배포판들과 그 밖의 많은 배포판)은 /usr/bin/python3 바이너리를 제공하는데, 이 바이너리는 최종 사용자가 사용할 수 있는 python3 명령을 제공할 뿐만 아니라 배포판에 포함된 Python 언어 소프트웨어를 위한 #!/usr/bin/python3 셔뱅도 제공합니다. Linux/UNIX용 Python 공식 바이너리 릴리스가 없기 때문에, 이러한 OS의 Python 최종 사용자는 거의 모두 자신의 배포판에서 빌드하여 제공하는 Python 인터프리터를 사용합니다.

배포판 사용자가 사용할 수 있는 python3 실행 파일과 배포판 내 다른 소프트웨어의 의존성으로 제공되는 python3 실행 파일은 일반적으로 동일한 바이너리입니다. 이는 최종 사용자가 가상 환경 밖에서 pip 같은 도구를 사용해 Python 패키지를 설치하면, 그 패키지가 배포판이 제공하는 Python 언어 소프트웨어에도 보이게 된다는 뜻입니다. 새로 설치된 패키지(또는 그 의존성 중 하나)가 배포판을 통해 설치된 패키지의 하위 호환성이 없는 새 버전이라면, 배포판이 제공하는 소프트웨어가 망가질 수 있습니다.

이는 배포판의 무결성에 심각한 문제를 일으킬 수 있습니다. 배포판에는 그 자체가 Python으로 작성된 패키지 관리 도구가 있는 경우가 많기 때문입니다. 예를 들어, pip install 명령으로 Fedora의 dnf 명령을 의도치 않게 망가뜨릴 수 있으며, 그렇게 되면 복구하기가 어렵습니다.

이는 시스템 전역 설치(sudo pip install)와 사용자 홈 디렉터리 설치(pip install --user) 모두에 해당합니다. 두 위치 중 어디에 있는 패키지든 /usr/bin/python3의 sys.path에 나타나기 때문입니다.

시스템 전역 설치에는 더 심각한 문제가 있습니다. sudo pip uninstall로 이 상황을 복구하려고 하면, 시스템 패키지 관리자가 제공한 패키지를 제거하게 될 수 있습니다. 사실 이는 단순히 패키지를 업그레이드하기만 해도 일어날 수 있습니다. pip가 OS가 제공한 이전 버전의 패키지를 제거하려고 하기 때문입니다. 이 시점에서는 시스템에 남아 있는 소프트웨어만으로는 시스템을 일관된 상태로 복구하지 못할 수도 있습니다.

지난 수년간, (배포판의 패키지를 사용하지 않는 경우) Python 라이브러리나 애플리케이션을 설치하는 가장 좋은 방법은 가상 환경을 사용하는 것이라는 합의가 형성되었습니다. 이 방식은 PyPA의 virtualenv 프로젝트를 통해 널리 알려졌으며, 이 방식의 간단한 버전은 현재 Python 표준 라이브러리에 venv로 포함되어 있습니다. Python 패키지를 virtualenv에 설치하면, 경로를 명시하지 않은 /usr/bin/python3 인터프리터에 그 패키지가 보이지 않게 되고 시스템 소프트웨어가 망가지는 것도 막을 수 있습니다.

그러나 경우에 따라서는 배포판이 제공하는 명령의 동작에 영향을 주는 Python 패키지를 배포판 외부에서 설치하는 것이 유용하며 의도된 일이기도 합니다. 이는 Sphinx나 Ansible처럼 Python 언어로 확장을 작성할 수 있는 메커니즘을 갖춘 소프트웨어에서 흔히 볼 수 있습니다. 사용자는 (유료 지원이나 보안 업데이트 등의 이유로) 기반 소프트웨어는 배포판의 버전을 사용하면서 PyPI에서 작은 확장을 설치하고 싶을 수 있으며, 그 확장을 기반 시스템의 소프트웨어가 임포트할 수 있기를 원할 것입니다.

이 방식에는 운영 체제가 예상하는 것보다 새로운 버전의 의존성을 설치하거나 그 밖의 방식으로 애플리케이션의 동작에 악영향을 줄 위험이 여전히 따르지만, 운영 체제의 파일을 제거할 위험까지 따를 필요는 없습니다. pip 같은 도구는 명시적으로 요청받은 경우 시스템 패키지 관리자가 소유한 파일을 삭제하지 않고도 기본 sys.path의 어떤 디렉터리에 패키지를 설치할 수 있어야 합니다.

따라서 이 PEP는 두 가지를 제안합니다.

첫째, 이 PEP는 Python 인터프리터 배포자가 해당 인터프리터의 패키지가 Python 외부 수단으로 관리된다고 표시하는 방법을 제안합니다. 이에 따라 pip 같은 Python 전용 도구는 명시적으로 재정의되지 않는 한 인터프리터의 전역 sys.path에 설치된 패키지를 어떤 방식으로도(추가, 업그레이드/다운그레이드, 제거) 변경해서는 안 됩니다. 또한 배포자가 대안으로 가상 환경을 사용하는 방법을 안내할 수 있는 수단도 제공합니다.

이것은 옵트인 메커니즘입니다. 기본적으로 업스트림 소스에서 컴파일한 Python 인터프리터에는 이러한 표시가 되어 있지 않으므로, 직접 컴파일한 인터프리터나 인터프리터를 명시적으로 표시하지 않은 배포판에서 pip install을 실행하면 항상 그래 왔던 대로 동작합니다.

둘째로, 인터프리터의 전역 컨텍스트에 패키지를 설치할 때(표시되지 않은 인터프리터에 설치하거나 표시를 무시하고 설치하는 경우) Python 전용 패키지 관리자는 자신이 파일을 생성할 sysconfig 스킴의 디렉터리 안에서만 파일을 수정하거나 삭제해야 한다는 규칙을 정합니다. 이렇게 하면 Python 인터프리터의 배포자는 자신이 관리하는 패키지용 디렉터리와 최종 사용자가 설치하는 관리되지 않는 패키지용 디렉터리, 이렇게 두 개의 디렉터리를 마련하고, 관리되지 않는 패키지를 설치하더라도 외부 패키지 관리자가 소유한 파일이 삭제(또는 덮어쓰기)되지 않도록 보장할 수 있습니다.

근거

다음 절에서 자세히 설명하듯이, 첫 번째 동작 변경은 EXTERNALLY-MANAGED라는 이름의 마커 파일을 생성하는 것으로, 이 파일이 존재하면 가상 환경이 아닌 곳의 패키지 설치가 배포판의 패키지 관리자처럼 Python 외부의 수단으로 관리된다는 뜻입니다. 이 파일은 기본 sysconfig 스킴의 stdlib 디렉터리에 두도록 규정되어 있습니다. 이 파일은 sys.path의 특정 위치가 아니라 인터프리터/설치 전체를 표시합니다. 그 이유는 앞에서 밝혔듯이 외부에서 관리되는 Python을 망가뜨릴 위험이 있는 관련 문제가 두 가지 있기 때문입니다. 하나는 호환되지 않는 새 버전의 패키지를 시스템 전체에 설치하는 경우(예: sudo pip install을 사용하여)이고, 다른 하나는 자신의 사용자 계정에만 설치하지만 그 위치가 표준 Python 명령의 sys.path에 포함된 경우(예: pip install --user를 사용하여)입니다. 마커 파일이 시스템 전역 site-packages 디렉터리에 있다면 두 번째 경우에는 명확하게 적용되지 않을 것입니다. 가능한 위치는 Alternatives 절에서 더 자세히 논의합니다.

두 번째 동작 변경은 이미 이런 종류의 문제를 겪은 배포판에 존재하는 sysconfig 설정을 활용하며, 특히 Python 전용 패키지 관리자가 외부 패키지 관리자 소유의 파일을 삭제하거나 덮어쓰는 문제를 다룹니다.

사용 사례

이 PEP에서 바뀌는 동작은 가능한 한 많은 사용 사례에서 “올바르게 동작하는 것”을 목표로 합니다. 이 절에서는 몇 가지 대표적인 사용 사례/컨텍스트에 대해 이 PEP가 규정하는 변경 사항을 살펴봅니다. 구체적으로는 이 PEP로 인해 바뀔 수 있는 다음 두 가지 동작에 대해 묻습니다.

  1. 이 PEP가 구현된 후에도 pip install과 같은 Python 전용 설치 프로그램 도구가 기본적으로 설치를 허용합니까?
  2. 그런 도구를 실행한다면, 해당 컨텍스트에서 배포판 패키지 관리자와 같은 외부(Python 전용이 아닌) 패키지 관리자가 제공한 패키지를 삭제해도 되겠습니까?

(간단히 하기 위해 이 절에서는 Python 전용 설치 프로그램 도구로 pip를 다루지만, 이 분석은 다른 모든 Python 전용 패키지 관리 도구에도 똑같이 적용됩니다.)

다음 표는 아래에서 자세히 다루는 사용 사례를 요약한 것입니다.

사례 설명 pip install 허용 외부에서 설치한 패키지 삭제 허용
1 패치되지 않은 CPython 현재는 예이며, 앞으로도 예입니다. 현재는 예이며, 앞으로도 예입니다.
2 배포판 /usr/bin/python3 현재 허용, 불허로 변경(배포판이 마커 파일을 추가한다고 가정) 현재 허용(Debian 제외), 불허로 변경
3 venv 안의 배포판 Python 현재는 예이며, 앞으로도 예입니다. 외부에서 설치된 패키지가 없습니다.
4 --system-site-packages를 사용한 venv 안의 배포판 Python 현재는 예이며, 앞으로도 예입니다. 현재는 아니요이며, 앞으로도 아니요입니다.
5 Docker 안의 배포판 Python 현재는 예이며, 아니요로 바뀝니다(배포판이 마커 파일을 추가한다고 가정합니다). 현재는 예이며, 아니요로 바뀝니다.
6 Conda 환경 현재는 예이며, 앞으로도 예입니다. 현재는 예이며, 앞으로도 예입니다.
7 개발자 대상 배포판 현재는 예이며, 아니요로 바뀝니다(마커 파일을 추가한다고 가정합니다). 현재는 대개 예이며, 아니요로 바뀝니다(필요에 따라 sysconfig를 구성한다고 가정합니다).
8 패키지를 빌드하는 배포판 현재는 예이며, 계속 예로 유지될 수 있습니다. 현재는 예이며, 아니요로 바뀝니다.
9 배포판 Python 표준 라이브러리에서 복사한 PYTHONHOME 현재는 예이며, 아니요로 바뀝니다. 현재는 예이며, 아니요로 바뀝니다.
10 업스트림 Python 표준 라이브러리에서 복사한 PYTHONHOME 현재는 예이며, 앞으로도 예입니다. 현재는 예이며, 앞으로도 예입니다.

위의 사용 사례를 더 자세히 설명하면 다음과 같습니다:

  1. 특별한 sysconfig 설정이나 패치가 없고 마커 파일도 없는, 패치되지 않은 표준 CPython입니다. 이 PEP는 이 경우의 동작을 바꾸지 않습니다.

    (이 PEP와 관계없이) 이러한 CPython은 같은 시스템에 배포판이 설치한 Python과 겹치는 방식으로 설치해서는 안 됩니다. 예를 들어 /usr/bin에 Python을 탑재하는 OS에서는 ./configure --prefix=/usr로 빌드한 사용자 정의 CPython을 설치해서는 안 됩니다. 그렇게 하면 배포판의 일부 파일을 덮어쓰게 되고, 결국 배포판도 여러분이 설치한 파일 일부를 덮어쓰게 됩니다. 그 대신 별도의 디렉터리(예를 들어 /usr/local, /opt 또는 홈 디렉터리)에 설치해야 합니다.

    따라서 이러한 CPython은 배포판이 설치한 어떤 Python과도 겹치지 않는 자체 stdlib 디렉터리와 자체 sysconfig 스킴을 가진다고 가정할 수 있습니다. 그러므로 OS가 설치한 패키지는 여기에서 보이지 않으며 관련도 없습니다.

    이 경우에 “외부 설치” 패키지라는 개념이 있다면, 그것은 OS 바깥의 것이며 일반적으로 이 CPython을 빌드하고 설치한 사람이 관리합니다. 설치 프로그램은 마커 파일을 추가하거나 sysconfig 스킴을 수정하지 않기로 했으므로 현재 동작을 선택한 것입니다. 따라서 pip install은 이 CPython에서 사용할 수 있는 모든 패키지를 제거할 수 있습니다.

  2. Recommendations for distros를 따르는 배포판의 /usr/bin/python3로, root로 pip install을 실행하거나 pip install --user를 실행하는 경우입니다.

    이 권고 사항에는 기본적으로 pip install을 막기 위해 stdlib 디렉터리에 마커 파일을 함께 배포하는 것과, root로 실행한 pip가 배포판이 제공하는 패키지의 위치에 쓰지 않도록 그 패키지들을 기본 sysconfig 스킴이 아닌 다른 위치에 두는 것이 포함됩니다.

    많은 배포판(Debian, Fedora 및 그 파생 배포판 포함)이 이미 후자를 따르고 있습니다.

    Debian과 그 파생 배포판에서는 현재 pip install이 배포판이 설치한 패키지를 삭제하지 않습니다. Debian이 이를 막는 pip 패치를 적용하고 있기 때문입니다. 따라서 이러한 배포판에서 이 PEP는 동작 변경이 아닙니다. 단지 그 동작을 더 이상 Debian에 국한되지 않고 업스트림 pip에 포함될 수 있는 방식으로 표준화할 뿐입니다.

    (Debian이나 그 파생 배포판에서 외부 설치 패키지가 삭제되었다는 사용자 보고를 본 적이 있습니다. 이는 사용자가 이전에 sudo pip install --upgrade pip를 실행하여 Debian 패치가 없는 버전의 /usr/bin/pip를 갖게 되었기 때문이라고 추측합니다. 업스트림 패키지 설치 프로그램에서 이 동작을 표준화하면 이 문제가 해결될 것입니다.)

  3. (venv나 virtualenv로 만든) 가상 환경 안에서 사용하는 배포판 Python입니다.

    가상 환경 안에서는 모든 패키지를 해당 환경이 소유합니다. pip, setuptools 등이 환경에 설치되어 있더라도, 이들은 해당 환경 전용 도구로 관리되며 또 그렇게 관리되어야 합니다. 이들은 시스템이 관리하는 것이 아닙니다.

  4. --system-site-packages를 사용한 가상 환경 안에서 사용하는 배포판 Python입니다. 앞의 경우와 비슷하지만, 전역 sys.path에 있는 모든 것이 보이므로 따로 명시할 가치가 있습니다.

    현재 “pip가 외부 설치 패키지를 삭제합니까?”라는 질문의 답은 ‘아니요’입니다. pip에는 가상 환경에서 실행하면서 환경 바깥의 패키지를 삭제하려 하는 경우를 위한 특별 처리가 있기 때문입니다. 이 PEP 이후에도 답은 여전히 ‘아니요’이지만 근거가 더 일반적으로 바뀝니다. 시스템 site 패키지는 환경에서 패키지 관리에 사용하는 어떤 sysconfig 스킴에도 속하지 않게 됩니다.

  5. 단일 애플리케이션 컨테이너 이미지(예: Docker 컨테이너)에서 사용하는 배포판 Python입니다. 이 사용 사례에서는 일반적으로 컨테이너 안에서 애플리케이션 하나만 실행되므로 시스템 소프트웨어가 망가질 위험이 더 낮습니다. 또한 컨테이너를 다시 빌드하면 되고 실행 중인 머신을 복구하느라 애쓸 필요가 없으므로 그 영향도 더 작습니다. 또한 아무 조건 없이 RUN pip install ... 문 등을 사용하는 기존 Dockerfile이 아주 많으며, 이들을 망가뜨리지 않는 것이 좋습니다. 따라서 베이스 컨테이너 이미지를 빌드하는 사람은 기반 OS가 기본적으로 마커 파일을 제공하더라도 마커 파일이 존재하지 않도록 하는 것이 좋을 수 있습니다.

    작은 동작 변경이 하나 있습니다. 현재는 root로 실행한 pip가 외부 설치 패키지를 삭제하지만, 이 PEP 이후에는 삭제하지 않습니다. 이를 재정의하는 방법은 제안하지 않습니다. 그러나 베이스 이미지는 일반적으로 최소한으로 구성되므로, (특히 배포판 자체 도구를 사용하지 않고) 패키지를 단순히 제거하기만 하는 사용 사례는 많지 않을 것입니다. 흔한 경우는 pip가 패키지를 업그레이드하려는 경우이며, 이전에는 (Debian을 제외하면) 이때 이전 버전이 삭제되었습니다. 이 변경 이후에는 이전 버전이 디스크에 그대로 남지만, pip는 여전히 외부 설치 패키지를 섀도잉하므로 실제로는 호환성을 깨뜨리는 변경이 되지 않기에 충분하다고 봅니다. Python import 문은 여전히 새로 설치한 패키지를 가져옵니다.

    이렇게 할 방법이 필요해진다면, 배포판이 자체적으로 사용하는 sysconfig 스킴에 설치 프로그램 도구가 접근하는 방법을 배포판에서 문서화할 것을 제안합니다. 자세한 논의는 Recommendations for distros 절을 참조하십시오.

    이 PEP의 저자들은 단일 애플리케이션 컨테이너 이미지에서도 배포판이 설치한 Python 인터프리터와 함께 가상 환경을 사용하는 것이 여전히 좋은 방법이라고 봅니다. 단일 애플리케이션을 실행하더라도 그 애플리케이션이 Python으로 구현된 OS 명령을 실행할 수 있으며, Python 전용 도구로 배포판이 제공하는 Python 패키지를 설치하거나 업그레이드했다면 그 명령들이 망가질 수 있습니다.

  6. Conda는 Conda 저장소에서 제공되지 않는 소프트웨어를 설치하기 위해 pip 같은 conda가 아닌 도구를 사용하는 것을 명시적으로 지원합니다. 이 맥락에서 Conda는 외부 패키지 관리자 / 배포판 역할을 하고, pip은 Python 전용 패키지 관리자 역할을 합니다.

    Conda가 Python 인터프리터 설치본을 자체적으로 제공한다는 점에서, 이는 어떤 의미로는 첫 번째 경우와 비슷합니다.

    저희는 이 PEP가 Conda에 어떠한 변경도 요구하지 않는다고 생각하며, 이 PEP의 변경 사항을 구현한 pip 버전은 Conda 환경 안에서 현재와 똑같이 계속 동작할 것입니다. (다만, 다른 배포판에서 좋은 방법인 것과 같은 이유로, pip으로 설치한 소프트웨어와 Conda로 설치한 소프트웨어에 별도의 sysconfig 스킴을 사용할지 고려해 볼 만할 수 있습니다.)

  7. “개발자 대상 배포판”이란, 배포판에 포함된 Python이나 다른 언어를 직접 사용하는 사용자가 라이브러리를 추가하고 싶을 때 배포판 자체를 변경할 것으로 기대되거나 그렇게 하도록 권장되는 특정 유형의 배포판을 뜻합니다. 흔한 예로는 소프트웨어 개발 회사의 비공개 “모노레포”가 있습니다. 이 경우 하나의 저장소에서 서드파티 소프트웨어와 사내 소프트웨어를 모두 빌드하며, 배포판 Python 인터프리터를 직접 사용하는 사람은 대개 해당 사내 소프트웨어를 작성하는 소프트웨어 개발자입니다. Nixpkgs 같은 사용자 수준 패키지 관리자도 여기에 해당할 수 있는데, Python 개발자인 Nix 사용자에게 자신의 소프트웨어를 Nix용으로 패키징하도록 권장하기 때문입니다.

    이러한 경우 배포판은 pip install 시도에 대해, 새 패키지를 추가할 때 배포판 자체의 기능을 사용하도록 권장하는 안내와 함께 문서 링크를 제공하는 방식으로 대응하고 싶을 수 있습니다.

    배포판이 배포판의 Python 인터프리터로 가상 환경을 만드는 것을 지원하거나 권장한다면, 가상 환경을 올바르게 설정하는 방법에 대한 별도의 안내가 있을 수도 있습니다(예를 들어 Nixpkgs가 그렇게 합니다).

  8. 배포판 Python(경우 2)을 위한 배포판 Python 패키지를 빌드할 때, 배포판의 패키지 빌드 과정의 일부로 pip install을 사용할 수 있으면 유용할 수 있습니다. (예를 들어, xyz의 sdist / 소스 tarball 안에서 pip install .을 사용하여 python-xyz RPM을 빌드하는 경우를 생각해 보십시오.) 배포판은 installer 같이 용도가 더 한정적이지만 여전히 Python 전용인 설치 도구를 사용하고 싶을 수도 있습니다.

    이 경우 빌드 과정에서는 pip install이 동작하도록 마커 파일을 무시하게 할 방법을 찾아야 하며, 아마도 Python 전용 도구가 기본 제공 스킴 대신 배포판의 sysconfig 스킴을 가리키도록 해야 할 것입니다. 이를 구현하는 방법에 대한 자세한 논의는 Recommendations for distros 절을 참조하십시오.

    이 PEP로 인해 pip은 시스템에 이미 있는 패키지를 더 이상 제거할 수 없게 됩니다. 그러나 패키지 빌드 과정은 시스템의 다른 파일을 삭제하는 지시를 포함해서는 안 되며(그리고 일반적으로 포함할 수도 없으며) 자신의 파일만 패키징할 수 있으므로, 이 동작 변경은 문제가 되지 않습니다.

  9. (가상 환경이 아닌) 대체 Python 환경을 구성하기 위해 PYTHONHOME과 함께 사용하는 배포판 Python으로, PYTHONHOME이 배포판 Python에서 직접 복사한 디렉터리로 설정된 경우입니다(예: cp -a /usr/lib/python3.x pyhome/lib).

    수정 사항이 없다고 가정하면, 동작은 기반이 되는 배포판 Python(경우 2)과 똑같습니다. 따라서 동작이 달라집니다. 기본적으로 더 이상 pip install을 할 수 없으며, 이를 무시하도록 설정하더라도 외부에서 설치된 패키지(즉, OS에서 복사되어 OS가 관리하는 sys.path 항목에 있는 Python 패키지)를 더 이상 삭제하지 않습니다.

    PYTHONHOME이 배포판 Python을 그대로 복사한 것이라면 배포판 Python처럼 동작해야 한다는 점에서, 이 동작 변경은 정당화할 수 있어 보입니다.

  10. 호환되며 수정되지 않은 업스트림 Python에서 가져온 PYTHONHOME과 함께 사용하는 배포판 Python(또는 임의의 Python 인터프리터)입니다.

    이 PEP의 동작 변경은 표준 라이브러리 안의 파일(stdlib 안의 마커 파일과 sysconfig 모듈의 동작)을 기준으로 결정되므로, 동작은 수정되지 않은 업스트림 CPython(경우 1)과 똑같습니다.

명세

인터프리터가 외부 패키지 관리자를 사용한다고 표시하기

Python 전용 패키지 설치 프로그램(즉, apt 같은 외부 도구가 아닌 pip 같은 도구)이 특정 Python 컨텍스트에 패키지를 설치하기 전에는 기본적으로 다음 검사를 수행해야 합니다.

  1. 가상 환경 밖에서 실행되고 있습니까? 이는 sys.prefix == sys.base_prefix인지로 판단할 수 있습니다(단, Backwards Compatibility 절을 참조하십시오).
  2. sysconfig.get_path("stdlib", sysconfig.get_default_scheme())로 식별되는 디렉터리에 EXTERNALLY-MANAGED 파일이 있습니까?

두 조건이 모두 참이면, 설치 프로그램은 가상 환경 밖에서는 이 Python 인터프리터의 디렉터리에 패키지를 설치하는 것이 비활성화되어 있다는 오류 메시지와 함께 종료해야 합니다.

설치 프로그램은 사용자가 이 규칙을 재정의할 수 있는 방법(예: --break-system-packages 같은 명령줄 플래그)을 제공해야 합니다. 이 옵션은 기본으로 활성화되어서는 안 되며, 사용하는 것이 위험하다는 뉘앙스를 담고 있어야 합니다.

EXTERNALLY-MANAGED 파일은 표준 라이브러리 configparser 모듈로 파싱할 수 있도록 만든 INI 형식의 메타데이터 파일입니다. 이 파일을 UTF-8 인코딩으로 configparser.ConfigParser(interpolation=None)를 사용해 파싱할 수 있고 파일에 [externally-managed] 섹션이 있다면, 설치 프로그램은 파일에 지정된 오류 메시지를 찾아 자신의 오류 출력의 일부로 출력해야 합니다. locale.getlocale(locale.LC_MESSAGES)가 반환하는 튜플의 첫 번째 요소, 즉 언어 코드가 None이 아니라면, Error- 뒤에 언어 코드가 붙은 이름의 키 값에서 오류 메시지를 찾아야 합니다. 그 키가 존재하지 않고 언어 코드에 밑줄이나 하이픈이 포함되어 있다면, Error- 뒤에 언어 코드 중 밑줄이나 하이픈 앞부분이 붙은 이름의 키를 찾아야 합니다. 이 두 키를 모두 찾을 수 없거나 언어 코드가 None이라면, 단순히 Error라는 이름의 키를 찾아야 합니다.

설치 프로그램이 파일에서 오류 메시지를 찾을 수 없다면(파일을 파싱할 수 없거나 적절한 오류 키가 없어서), 설치 프로그램은 자체적으로 미리 정의한 오류 메시지를 사용하면 되며, 이 메시지는 패키지를 설치하려면 가상 환경을 만들라고 사용자에게 제안해야 합니다.

자신의 Python 패키지의 sys.path에 있는 라이브러리를 Python 전용이 아닌 패키지 관리자로 관리하는 소프트웨어 배포자는 일반적으로 표준 라이브러리 디렉터리에 EXTERNALLY-MANAGED 파일을 함께 배포해야 합니다. 예를 들어 Debian은 다음과 비슷한 내용으로 이루어진 파일을 /usr/lib/python3.9/EXTERNALLY-MANAGED에 배포할 수 있습니다.

[externally-managed]
Error=To install Python packages system-wide, try apt install
 python3-xyz, where xyz is the package you are trying to
 install.

 If you wish to install a non-Debian-packaged Python package,
 create a virtual environment using python3 -m venv path/to/venv.
 Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
 sure you have python3-full installed.

 If you wish to install a non-Debian packaged Python application,
 it may be easiest to use pipx install xyz, which will manage a
 virtual environment for you. Make sure you have pipx installed.

 See /usr/share/doc/python3.9/README.venv for more information.

이 파일은 패키지를 설치하려는 사용자에게 유용하고 배포판에 맞는 정보를 제공합니다. 선택적으로, 같은 파일 안에 번역을 제공할 수도 있습니다:

Error-de_DE=Wenn ist das Nunstück git und Slotermeyer?

 Ja! Beiherhund das Oder die Virtualenvironment gersput!

생성 후 업데이트하지 않는 단일 애플리케이션 컨테이너 이미지처럼 특정한 상황에서는, 사용자가 이 규칙을 수동으로 재정의하지 않고도 (현재처럼) 원하는 것을 무엇이든 설치할 수 있도록 배포자가 EXTERNALLY-MANAGED 파일을 배포하지 않기로 선택할 수 있습니다.

대상 sysconfig 스킴에만 쓰기

일반적으로 Python 패키지 설치 프로그램은 sysconfig 표준 라이브러리 패키지가 반환하는 스킴의 디렉터리에 설치합니다. 보통 이 스킴은 sysconfig.get_default_scheme()가 반환하는 스킴이지만, 설정(예: pip install --user)에 따라 다른 스킴을 사용할 수도 있습니다.

이 PEP는 설치 프로그램이 sysconfig 스킴에 설치할 때는 언제나 그 스킴 바깥의 파일을 절대 수정하거나 삭제해서는 안 된다고 규정합니다. 예를 들어 패키지를 업그레이드하는데 그 패키지가 이미 해당 스킴 바깥의 디렉터리(아마도 다른 스킴의 디렉터리)에 설치되어 있다면, 기존 파일은 그대로 두어야 합니다.

설치 프로그램이 업그레이드 도중 결과적으로 기존 설치를 가리게 된다면, 실행이 끝날 때 경고를 출력할 것을 권장합니다.

설치 프로그램이 sysconfig 스킴 바깥의 위치에 설치하는 경우(예: pip install --target)에는 이 하위 절이 적용되지 않습니다.

배포판을 위한 권장 사항

이 절은 규범적이지 않습니다. 이 절은 특별한 이유가 없는 한 배포판이 따라야 한다고 저희가 생각하는 모범 사례를 제시합니다.

설치를 외부에서 관리되는 것으로 표시하기

배포판은 자신의 stdlib 디렉터리에 EXTERNALLY-MANAGED 파일을 만들어야 합니다.

사용자를 가상 환경으로 안내하기

이 파일에는 배포판의 패키지 관리자를 통해 시스템 전역 패키지를 설치하는 방법과 가상 환경을 설정하는 방법을 모두 알려 주는, 유용하고 배포판에 맞는 오류 메시지가 담겨 있어야 합니다. 여러분의 배포판을 사용하는 사용자가 python3 명령은 사용할 수 있지만(특히 pip 또는 get-pip도 사용할 수 있지만) python3 -m venv는 작동하지 않는 상태인 경우가 많다면, 메시지에 python3 -m venv가 제대로 작동하게 하는 방법을 명확히 안내해야 합니다.

Python 언어로 작성된 애플리케이션을 설치하는 도구인 pipx를 패키징하고 오류 메시지에서 이를 제안하는 것을 고려하십시오. pipx는 해당 애플리케이션만을 위한 가상 환경을 자동으로 만들어 주는데, 이는 (배포판에서 제공하지 않는) Python 언어 소프트웨어를 설치하고 싶지만 스스로는 Python 사용자가 아닌 최종 사용자에게 훨씬 나은 기본 방식입니다. 배포판에서 pipx를 패키징하면, 시스템 패키지가 망가지는 것을 피하기 위해 사용자에게 pip install --user --break-system-packages pipx를 실행하라고 안내하는 역설적인 상황을 피할 수 있습니다. 최종 사용자를 위한 배포판의 Python 패키지나 환경(예: Fedora의 python3 또는 Debian의 python3-full)이 pipx에 의존하도록 구성하는 것을 고려하십시오.

컨테이너 이미지에 표시 파일 유지하기

단일 애플리케이션 컨테이너용 공식 이미지(예: Docker 컨테이너 이미지)를 만드는 배포판은 EXTERNALLY-MANAGED 파일을 유지해야 합니다. 이미지 사용자가 이미지 안에서 패키지 업데이트를 설치하더라도(RUN apt-get dist-upgrade를 떠올려 보십시오) 이 파일이 사라지지 않는 방식으로 유지하는 것이 좋습니다.

배포판 디렉터리와 로컬 디렉터리 분리하기

배포판은 시스템 인터프리터의 sys.path에 서로 다른 두 경로를 두어야 합니다. 하나는 배포판이 설치한 패키지용이고, 다른 하나는 로컬 시스템 관리자가 설치한 패키지용입니다. 그리고 sysconfig.get_default_scheme()이 후자의 경로를 가리키도록 설정해야 합니다. 이렇게 하면 pip 같은 도구가 배포판이 설치한 패키지를 수정하지 않습니다. 로컬에 설치한 패키지가 배포판 패키지보다 우선하도록, sys.path에서 로컬 시스템 관리자용 경로가 배포판 경로보다 앞에 와야 합니다.

예를 들어, Fedora와 Debian(그리고 그 파생 배포판)은 모두 로컬에 설치한 패키지에는 /usr/local을, 배포판이 설치한 패키지에는 /usr을 사용하여 이러한 분리를 구현합니다. Fedora는 /usr/local/lib/python3.x/site-packages와 /usr/lib/python3.x/site-packages를 나누어 사용합니다. (Debian은 로컬에서 컴파일한 Python 인터프리터와 한 단계 더 분리하기 위해 /usr/local/lib/python3/dist-packages와 /usr/lib/python3/dist-packages를 사용합니다. 업스트림 CPython을 빌드하여 /usr/local/bin에 설치하면 이 인터프리터는 /usr/local/lib/python3/site-packages를 참조합니다. Debian은 로컬에서 빌드한 인터프리터로 설치한 패키지가 배포판 인터프리터의 sys.path에 나타나지 않도록 하려는 것입니다.)

/usr/local과 /usr의 분리는 PATH 환경 변수가 일반적으로 /usr/local/bin:/usr/bin을 포함하고, 배포판에 속하지 않은 소프트웨어가 기본적으로 /usr/local에 설치되는 방식과 유사하다는 점에 유의하십시오. 이러한 분리는 Filesystem Hierarchy Standard에서 권장하는 방식입니다.

이를 구현하는 방법은 두 가지가 있습니다. 첫 번째 방법은 Python 라이브러리를 직접 빌드하고 패키징하는 경우(예: 패키징 도우미가 PEP 517로 빌드한 휠을 풀거나 setup.py install을 호출하는 경우), 해당 도구가 sysconfig 스킴에는 없지만 sys.path에는 있는 디렉터리를 사용하도록 구성하는 것입니다.

두 번째 방법은 패키지 빌드 안에서 실행될 때와 설치된 시스템에서 실행될 때 기본 sysconfig 스킴이 달라지도록 구성하는 것입니다. bpo-43976의 sysconfig 사용자 정의 훅이 수락되어 구현되면 이 작업이 쉬워질 것입니다. 패키징 도구가 환경 변수나 그 밖에 감지할 수 있는 설정을 지정하게 하고, 패키지 빌드 안에서 호출되었을 때 다른 스킴을 반환하도록 get_preferred_schemes 함수를 정의하십시오. 그러면 배포판 패키징 과정의 일부로 pip install을 사용할 수 있습니다.

pip가 특정 스킴을 대상으로 실행되도록 지시하는 --scheme=... 옵션을 추가할 것을 제안합니다. (현재 pip가 스킴을 결정하는 방식은 아래의 Implementation Notes를 참고하십시오.) 이 옵션이 추가되면, 로컬 테스트용으로, 그리고 경우에 따라 실제 패키징에서도 pip install --scheme=posix_distro와 같은 명령을 실행하여 (get_preferred_schemes를 거치지 않고) 배포판의 위치에 패키지를 명시적으로 설치할 수 있습니다. 꼭 필요하다면 pip uninstall --scheme=posix_distro를 사용하여 시스템이 관리하는 디렉터리에서 pip로 패키지를 제거할 수도 있습니다. 이를 통해 Rationale의 사용 사례 5에서 설명한 (이론상으로만 존재하기를 바라는) 기능 퇴행 문제도 해결됩니다.

pip로 패키지를 설치하려면, EXTERNALLY-MANAGED 표시 파일을 비활성화하여 pip가 실행되도록 하거나 명령줄에서 이 파일을 무시하도록 재정의해야 합니다. 빌드 chroot에서도 컨테이너 이미지에서와 같은 방법으로 표시 파일을 비활성화하는 것이 좋을 수 있습니다.

이러한 설정을 자동으로 적용되게 하면(빌드 환경에서 표시 파일을 비활성화하고 get_preferred_schemes가 자동으로 배포판의 스킴을 반환하게 하면) 패키지 빌드 안에서 아무 옵션 없이 pip install만 실행해도 동작한다는 장점이 있습니다. 이는 대개 내부적으로 pip install을 호출하는 업스트림 빌드 스크립트를 수정하지 않아도 올바르게 동작한다는 뜻입니다. 물론 패키징 과정에서 항상 pip install --scheme=posix_distro --break-system-packages를 호출하도록 하기만 해도 되며, 이 방법도 동작합니다.

여기서 가장 좋은 방법은 배포판의 패키징 관례와 메커니즘에 따라 크게 달라집니다.

마찬가지로, 임포트할 수 있는 Python 코드를 위한 것이 아닌 sysconfig 경로, 즉 include, platinclude, scripts, data도 두 가지 변형을 두어야 합니다. 하나는 배포판이 패키징한 소프트웨어용이고, 다른 하나는 로컬에 설치한 소프트웨어용입니다. 또한 배포판은 두 경로를 모두 사용할 수 있도록 구성해야 합니다. 예를 들어, 일반적인 FHS 준수 배포판은 기본 스킴의 include에 /usr/local/include를, 배포판이 패키징한 헤더에 /usr/include를 사용하고 두 경로를 모두 컴파일러의 검색 경로에 둡니다. 또한 기본 스킴의 scripts에 /usr/local/bin을, 배포판이 패키징한 진입점에 /usr/bin을 사용하고 두 경로를 모두 $PATH에 둡니다.

하위 호환성

이 메커니즘들은 모두 새 배포판 릴리스와 pip 같은 도구의 새 버전에만 적용하도록 제안하는 것입니다.

특히 메이저 버전 개념이 있는 배포판은 새 메이저 버전에서만 표시 파일을 추가하거나 sysconfig 스킴을 변경할 것을 강력히 권장합니다. 그렇지 않으면 기존 시스템에서 Python 전용 패키지 관리자로 설치한 소프트웨어를 (재정의 옵션 없이는) 더 이상 관리할 수 없게 될 위험이 있습니다. 롤링 릴리스 배포판에서는 가능하면 새 Python 마이너 버전에서만 표시 파일을 추가하거나 sysconfig 스킴을 변경하십시오.

패키지 설치 도구가 겪을 특히 까다로운 하위 호환성 문제 중 하나는, 도구의 최신 버전이 설치되어 있지만 오래된 버전의 virtualenv로 만든 환경을 관리하는 일일 것입니다. 이제 “가상 환경”은 꽤 정확한 정의를 갖습니다. 가상 환경은 pyvenv.cfg 메커니즘을 사용하며, 이로 인해 sys.base_prefix != sys.prefix가 됩니다. 그러나 사용자가 이전 버전의 virtualenv로 만든 오래된 가상 환경을 가지고 있을 수도 있습니다. 이 글을 쓰는 시점에 pip는 Python 3.6 이상을 지원하고, Python 3.6은 virtualenv 15.1.0 이상에서 지원되므로 이러한 상황이 생길 수 있습니다. 이전 버전의 virtualenv는 그 대신 sys.real_prefix라는 새 속성을 설정하는 메커니즘을 사용하며, 가상 환경에 대한 표준 라이브러리 지원을 사용하지 않습니다. 따라서 sys.base_prefix는 sys.prefix와 같습니다. 따라서 가상 환경을 견고하게 감지하는 로직은 대략 다음과 같습니다:

def is_virtual_environment():
    return sys.base_prefix != sys.prefix or hasattr(sys, "real_prefix")

보안 영향

이 기능의 목적은 보안 경계를 구현하는 것이 아닙니다. 선의로 이루어진 변경이 사용자의 환경을 예기치 않게 망가뜨리는 일을 막는 것이 목적입니다. 즉, 이 PEP가 가상 환경 밖에서의 pip install을 제한하는 이유는 그렇게 할 수 있는 것이 보안 위험이기 때문이 아닙니다. “분명한 방법이 하나– 가급적이면 단 하나만 –있어야 한다”는 원칙 때문이며, 그 방법은 가상 환경을 사용하는 것이어야 합니다. 가상 환경 밖에서의 pip install은 거의 언제나 잘못된 방법인데도 지나치게 분명한 방법으로 보입니다.

사용자가 보안상의 이유로 sudo pip install 또는 pip install --user를 실행해 sys.path에 파일을 추가할 수 없어야 하는 경우가 있다면, 이는 사용자가 쓸 수 있는 파일에 대한 접근 제어 규칙이나 해당 프로그램을 위해 명시적으로 보호된 sys.path를 통해 구현해야 합니다. 이 PEP의 메커니즘 중 어느 것도 그러한 시나리오를 해결하는 방법으로 해석해서는 안 됩니다.

이러한 이유로, 마커 파일이 있는 상태에서 설치를 시도하는 것은 보안 사고가 아니며, 이에 대해 감사 이벤트를 발생시킬 필요도 없습니다. 호출한 사용자에게 sudo pip install 또는 pip install --user를 사용할 정당한 권한이 있다면, 같은 설치를 Python 밖에서 완전히 수행할 수 있습니다. 그러한 권한이 정당하게 없다면, 그것은 이 PEP의 범위를 벗어나는 문제입니다.

마커 파일 자체는 표준 라이브러리 디렉터리에 있으며, 이곳은 신뢰할 수 있는 위치입니다(즉, 특정 설치 프로그램이 사용하는 마커 파일에 쓸 수 있는 사람이라면 아마 그 설치 프로그램 안에서 임의의 코드도 실행할 수 있을 것입니다). 따라서 일반적으로 오류 메시지에서 터미널 이스케이프 시퀀스나 기타 잠재적으로 악의적인 내용을 걸러낼 필요는 없습니다.

대안

우리가 검토했지만 이 PEP에서 기각하거나 보류한 비슷한 제안이 여럿 있으며, 이는 주로 Rationale의 사례별 분석에서 설명한 동작을 유지하기 위해서입니다.

마커 파일

마커 파일을 sys.path에 두어, 특정 디렉터리를 Python 전용 패키지 관리자가 쓰지 않아야 할 곳으로 표시해야 합니까? 이렇게 하면 이 PEP가 다루는 두 번째 문제(배포판 소유 파일을 덮어쓰거나 삭제하지 않는 것)에는 도움이 되지만, 첫 번째 문제(호환되지 않는 설치)에는 도움이 되지 않습니다. /usr/lib/python3.x/site-packages에 있는 디렉터리별 마커는 /usr/local/lib/python3.x/site-packages나 ~/.local/lib/python3.x/site-packages로의 설치를 막지 못하는데, 이 두 디렉터리 모두 /usr/bin/python3의 sys.path에 포함되어 있습니다. 다시 말해, 마커 파일은 (sys.path에 있는 디렉터리에 우연히 위치하더라도) 단일 디렉터리를 외부에서 관리되는 것으로 표시하는 것으로 해석해서는 안 됩니다. 마커 파일은 Python 설치 전체를 외부에서 관리되는 것으로 표시합니다.

위 제안의 또 다른 변형으로, 마커 파일을 sys.path에 두고 sys.path의 어느 디렉터리에서든 발견되면 해당 설치를 외부에서 관리되는 것으로 표시해야 합니까? 이 방식의 분명한 장점은 가상 환경에서 자동으로 비활성화된다는 것입니다. 안타깝게도, 이 방식은 시스템 전역 sys.path가 보이지만 패키지 설치는 허용되는 --system-site-packages 가상 환경에서 잘못 동작합니다. (가상 환경을 예외로 하는 규칙을 유지한다면 동작할 수는 있겠지만, 그렇게 해도 현재 방식에 비해 아무런 이점이 없어 보입니다.)

마커를 단순히 sysconfig 스킴의 새 속성으로 두어야 합니까? 개념적으로는 어느 정도 깔끔하지만, 재정의하기 어렵다는 단점이 있습니다. 우리는 컨테이너 이미지, 패키지 빌드 환경 등에서 마커 파일을 쉽게 무력화할 수 있기를 원합니다. 삭제할 수 있는 파일은 다루기 쉽지만, sysconfig에 있는 코드는 수정하기가 훨씬 어렵습니다.

파일을 /etc에 두어야 합니까? 아닙니다. 다시 말하지만, 이 파일은 특정 Python 설치를 가리키기 때문입니다. Python을 직접 설치한 사용자는 그 인터프리터의 전역 컨텍스트에 패키지를 설치하고 싶어 할 수 있습니다.

이 구성 설정을 pip.conf나 distutils.cfg에 두어야 합니까? 설치를 표시하는 것에 관한 위의 반대 의견과는 별개로, 이 메커니즘은 두 도구 중 어느 하나에만 해당하는 것이 아닙니다. (사용자가 어떤 Python 설치에서든 실수로 가상 환경 밖에 설치하는 것을 스스로 막을 수 있도록 pip이 별도로 구성 플래그를 구현하는 것은 합리적으로 보이지만, 이는 이 PEP의 범위를 벗어납니다.)

파일을 TOML 형식으로 해야 합니까? TOML은 패키징 분야에서 점점 인기를 얻고 있지만(예: PEP 517 참고), 아직 표준 라이브러리에 구현이 없습니다. 엄밀히 말하면 이것이 걸림돌은 아닙니다. 배포판은 파일을 읽을 필요 없이 쓰기만 하면 되므로 TOML 라이브러리가 필요하지 않고(형식과 관계없이 파일은 아마 손으로 작성될 것입니다), 패키징 도구에는 이미 TOML 읽기 기능이 있을 가능성이 높습니다. 그러나 INI 형식은 현재 다양한 다른 형태의 패키징 메타데이터(예: pydistutils.cfg와 setup.cfg)에 사용되고 있고, 우리의 요구를 충족하며, 표준 라이브러리로 파싱할 수 있습니다. 또한 pip 유지 관리자들은 아직 이 용도로 TOML을 사용하지 않기를 원한다는 의견을 밝혔습니다.

이 파일은 email.message 형식이어야 합니까? 이 형식은 패키징 메타데이터(예: sdist 및 wheel 메타데이터)에도 사용되고 표준 라이브러리로 파싱할 수도 있지만, 여러 줄 항목을 그다지 명확하게 처리하지 못합니다. 그런데 여러 줄 항목이 바로 저희의 주요 사용 사례입니다.

마커 파일이 설치를 허용할지 여부를 판단하는 실행 가능한 Python 코드여야 합니까? 파일을 sys.path에 두는 것에 대한 앞서 언급한 우려와는 별개로, 이 파일을 실행 가능하게 만들면 지나치게 강력한 API를 약속하게 되고 동작을 이해하기가 더 어려워질 위험이 있다는 우려가 있습니다. (bpo-43976의 get_default_scheme 훅은 실제로 실행 가능하지만, 그 코드는 인터프리터를 빌드할 때 제공되어야 하며 빌드 이후에 제공하도록 의도된 것이 아니라는 점에 유의하십시오.)

마커를 무시하고 설치할 때, Python 전용 패키지 관리자가 외부 패키지 관리자가 설치한 패키지를 가리는 것(즉, 같은 이름의 모듈을 설치하는 것)을 금지해야 합니까? 그렇게 하면 시스템 소프트웨어가 망가질 위험을 최소화할 수 있겠지만, 그로 인해 늘어나는 사용자 경험의 복잡성을 감수할 가치가 있는지는 분명하지 않습니다. 시스템 패키지를 가리는 데에는 정당한 사용 사례가 있으며, 이를 허용하기 위한 명령줄 옵션을 추가하면 더 혼란스러워질 것입니다. 한편, 그 옵션을 전달하지 않는다고 해서 시스템 소프트웨어가 망가질 위험이 없어지는 것도 아닙니다. 시스템 소프트웨어는 try: import xyz가 실패하는 것이나 제한된 진입점 집합을 찾는 것 등에 의존하고 있을 수 있기 때문입니다. 이러한 구분을 전달하기는 어려워 보입니다. Python 전용 패키지 관리자가 패키지를 가릴 때 경고를 출력하는 것은 좋은 생각이라고 보지만, 이를 기본적으로 비활성화할 가치는 없다고 생각합니다.

누가 패키지를 설치했는지, 그리고 그 패키지를 제거할 수 있는지를 판단하는 데 PEP 376의 INSTALLER 파일을 사용하지 않는 이유는 무엇입니까? 첫째, 이 파일은 특정 패키지에 한정되므로(패키지의 dist-info 디렉터리에 있습니다), 앞서 살펴본 몇몇 대안과 마찬가지로 환경 전체에 대한 정보나 패키지 설치가 허용되는지에 대한 정보를 제공하지 않습니다. 또한 PEP 627은 PEP 376을 갱신하여 INSTALLER를 프로그래밍 방식으로 사용하지 못하도록 하며, 이 파일은 “정보 제공 목적으로만 사용되어야 합니다. […] 저희의 목표는 상호 운용되는 도구를 지원하는 것이며, 어떤 도구가 패키지를 설치했는지에 따라 동작을 결정하는 것은 그 목표에 어긋납니다.”라고 명시합니다. 마지막으로, PEP 627이 내다보듯이 한 도구가 다른 도구로 설치된 패키지를 처리하는 방법을 아는 데에는 정당한 사용 사례가 있습니다. 예를 들어, conda는 pip이 Conda 환경에 설치한 패키지를 안전하게 제거할 수 있습니다.

이 명세는 왜 가상 환경 안에서 패키지 설치를 비활성화하는 수단을 제공하지 않습니까? 그에 대한 특별히 강력한 사용 사례를 찾을 수 없습니다(적어도 이 PEP의 목적과 관련된 사용 사례는 없습니다). 필요하다면 해당 환경 안에서 pip uninstall pip을 실행하기만 하면 되며, 이렇게 하면 적어도 의도하지 않은 환경 변경은 막을 수 있을 것입니다(그리고 결국 마커 파일은 쉽게 제거할 수 있으므로, 이 명세는 의도적인 변경을 막기 위한 조항을 두지 않습니다).

시스템 Python

배포판 소프트웨어는 sys.path에 배포판의 site-packages 디렉터리만 둔 채 실행하고, 사용자별 site-packages뿐 아니라 로컬 시스템 관리자의 site-packages도 무시해야 하지 않습니까? 이는 가치 있는 아이디어이며, “시스템 Python” 또는 “플랫폼 Python”이라는 이름으로 한동안 여러 버전이 논의되어 왔습니다(Python 코드를 작성하거나 시스템과 별개로 Python 소프트웨어를 설치하는 최종 사용자를 위한 별도의 “사용자 Python”과 함께). 하지만 이는 훨씬 더 많은 것이 얽힌 변경입니다. 우선, 이는 하위 호환성을 깨는 변경이 될 것입니다. Motivation 절에서 언급했듯이, Sphinx나 Ansible처럼 배포판이 설치한 Python 애플리케이션을 로컬에 설치된 Python 라이브러리를 sys.path에서 사용할 수 있는 상태로 실행하는 데에는 타당한 사용 사례가 있습니다. 로컬 패키지를 일괄적으로 무시하도록 전환하면 이러한 사용 사례가 깨질 것이며, 배포판은 각 애플리케이션이 로컬에 설치된 라이브러리를 볼 수 있어야 하는지를 하나하나 분석해야 할 것입니다.

게다가 Fedora는 이 변경을 시도했다가 되돌렸으며(Fedora attempted this change and reverted it), 아이러니하게도 그 변경의 구현이 자신들의 패키지 관리자를 망가뜨렸다는 사실(broke their package manager)을 발견했습니다. 그 경험을 고려하면, 배포판이 그 접근 방식을 안정적으로 구현하려면 해결해야 할 세부 사항이 분명히 남아 있으며, 이를 권장하는 PEP는 시기상조일 것입니다.

이 PEP는 배포자가 “시스템 Python”이나 그와 비슷한 제안을 채택하는지 여부와 관계없이 적용할 수 있는, 완결되고 독립적인 변경을 의도합니다. 이 PEP는 배포판이 향후 “system Python”을 구현하는 것과 양립할 수 없는 것이 아닙니다. 두 제안이 같은 종류의 문제를 다루기는 하지만, 이 PEP를 구현한 후에도 “system Python”과 같은 것을 구현하는 쪽을 지지하는 인자는 여전히 있습니다. 그러나 동시에 이 PEP는 특별히 더 목표가 분명하고 최소한의 변경을 하려고 하며, 그래서 “시스템 Python”을 채택할 계획이 없는(또는 당장 구현할 계획이 없는) 배포자도 구현할 수 있습니다. 이 PEP의 변경 사항은 그 자체로 가치가 있으며, 향후 어떤 제안을 위한 중간 단계가 아닙니다. 이 PEP는 호환성을 깨는 변경을 최소화하면서도(완전히 피하지는 못하지만) 시스템 소프트웨어가 망가질 위험을 줄이므로(완전히 없애지는 못하지만), 앞서 언급한 단점을 지닌 완전한 “시스템 Python” 아이디어보다 훨씬 구현하기 쉬울 것입니다.

이 PEP의 지침, 즉 사용자는 가능한 한 가상 환경을 사용해야 하고 배포판은 배포판이 관리하는 모듈과 로컬에서 관리하는 모듈에 대해 별도의 sys.path 디렉터리를 두어야 한다는 지침이 앞으로 추가적인 실험을 더 쉽게 만들어 줄 것으로 기대합니다. 이러한 실험에는 완전히 분리된 “시스템” Python 인터프리터와 “사용자” Python 인터프리터를 배포하는 것, 배포판 소유의 가상 환경이나 PYTHONHOME에서 시스템 소프트웨어를 실행하는 것(단, 인터프리터는 하나만 제공), 또는 특정 소프트웨어(배포판의 패키지 관리자 등)의 진입점을 수정하여 배포판이 관리하는 디렉터리만 보는 sys.path를 사용하게 하는 것 등이 포함될 수 있습니다. 하지만 그러한 아이디어 자체는 여전히 이 PEP의 범위를 벗어납니다.

구현 참고 사항

이 절은 비규범적이며, 명세와 잠재적인 구현 모두에 관련된 참고 사항을 담고 있습니다.

현재 pip는 대상 sysconfig 스킴을 직접 선택하는 방법을 제공하지 않지만, 설치할 때 스킴을 조회하는 방법은 세 가지가 있습니다:

pip install
sysconfig.get_default_scheme()을 호출합니다. 이는 보통 (업스트림 CPython과 현재 대부분의 배포판에서) get_preferred_scheme('prefix')와 같습니다.
pip install --prefix=/some/path
sysconfig.get_preferred_scheme('prefix')를 호출합니다.
pip install --user
sysconfig.get_preferred_scheme('user')를 호출합니다.

마지막으로, pip install --target=/some/path는 어떤 스킴도 조회하지 않고 /some/path에 직접 기록합니다.

Debian은 현재 몇 가지 휴리스틱(VIRTUAL_ENV 환경 변수 확인 포함)을 사용하는 가상 환경 내부의 기본 설치 위치를 변경하는 패치를 적용하고 있는데, 이는 주로 가상 환경에서 사용되는 디렉터리가 dist-packages가 아닌 site-packages로 유지되도록 하기 위함입니다. 해당 패치의 구현은 실제로 기본 sysconfig 스킴을 변경하지 않으며, 특히 sysconfig.get_path("stdlib")의 결과를 변경하지 않으므로, 이 제안에는 별다른 영향을 주지 않습니다.

Fedora는 현재 rpmbuild 내부에서 실행되지 않을 때 기본 설치 위치를 변경하는 패치를 적용하고 있으며, 이를 사용하여 두 개의 시스템 전역 디렉터리를 두는 방식을 구현합니다. 이는 개념적으로 bpo-43976에서 구상한 종류의 훅이지만, 변경된 sysconfig 스킴이 아니라 distutils에 대한 코드 패치로 구현되었다는 점이 다릅니다.

위의 is_virtual_environment 구현과 EXTERNALLY-MANAGED 파일을 불러와 그 안에서 오류 메시지를 찾는 로직은 구현을 한곳에 모으기 위해 표준 라이브러리(각각 sys와 sysconfig)에 추가해도 무방하지만, 아직 추가할 필요는 없습니다.

참고 문헌

이러한 문제와 이를 해결하려던 이전 시도에 대한 추가 배경은 2014년의 Debian bug 771794 “pip이 시스템이 제공한 Python 패키지를 조용히 제거/업데이트함”, sudo pip이 /usr/local을 가리키도록 하는 방법을 다룬 Fedora의 2018년 글 Making sudo pip safe (이 글은 해당 변경으로도 sudo pip이 완전히 안전해지지는 않는다는 점을 인정합니다), 2018년의 pip 이슈 5605 (“pip을 통해 설치되지 않은 기존 Python 모듈의 업그레이드 비활성화”)와 5722 (“pip은 /usr/local을 존중해야 함”), 그리고 PyCon US 2019 이후의 토론 스레드 Playing nice with external package managers를 참고하십시오.