PEP 582 – Python 로컬 패키지 디렉터리
- Author:
- Kushal Das <mail at kushaldas.in>, Steve Dower <steve.dower at python.org>, Donald Stufft <donald at stufft.io>, Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Rejected
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 16-May-2018
- Python-Version:
- 3.12
- Post-History:
- 01-Mar-2019
- Resolution:
- Discourse message
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 sys.path를 설정하는 기존 메커니즘을 확장하여, 기존 위치에 더해 새로운 __pypackages__ 디렉터리를 포함할 것을 제안합니다. 새로운 디렉터리는 sys.path의 시작 부분, 즉 현재 작업 디렉터리 다음이면서 시스템 site-packages 바로 앞에 추가되어, 해당 위치에 설치된 패키지가 다른 위치보다 우선하도록 합니다.
이는 현재 디렉터리(또는 스크립트가 위치한 디렉터리)를 추가하는 기존 메커니즘과 유사하지만, 하위 디렉터리를 사용함으로써 추가 라이브러리를 사용자의 작업과 분리하여 유지합니다.
동기
새로운 Python 프로그래머는 개별 프로젝트의 종속성을 시스템 환경에서 격리하는 것의 가치를 배움으로써 이점을 얻을 수 있습니다. 그러나 이를 수행하는 기존 메커니즘인 가상 환경은 초보자가 이해하기에 복잡하고 오류가 발생하기 쉬운 것으로 알려져 있습니다. 가상 환경을 설명하는 일은 초보자 그룹의 설정을 지원할 때 종종 방해 요소가 됩니다. 플랫폼과 셸 환경의 차이로 인해 개별적인 지원이 필요하며, 새로운 셸 세션마다 활성화해야 하므로 학생들이 쉬었다가 다시 작업을 시작할 때 실수하기 쉽습니다. 이 제안은 사용자가 더 고급 개념을 이해하지 않아도 격리를 제공하는 가벼운 해결책을 제시합니다.
또한 독립 실행형 Python 애플리케이션은 일반적으로 작동하기 위해 서드파티 라이브러리가 필요합니다. 일반적으로 이러한 애플리케이션은 가상 환경에서 실행되도록 설계되어 종속성이 애플리케이션과 함께 환경에 설치되거나, 하위 디렉터리에 종속성을 포함하고 애플리케이션 시작 시 sys.path를 수정합니다. 가상 환경은 일반적이고 효과적인 해결책이지만(예를 들어 pipx 도구에서 사용됨), 설정하고 관리하기가 다소 불편하며 재배치할 수 없습니다. 반면 sys.path를 수동으로 조작하는 것은 개발자가 올바르게 처리해야 하는 상용구 코드이며, 런타임 동작이므로 린터나 타입 검사기와 같은 도구는 이를 이해하지 못합니다. __pypackages__ 제안은 “번들 종속성” 위치라는 개념을 공식화하여 상용구 코드를 없애고, 개발 도구가 인식하도록 가르칠 수 있는 표준 위치를 제공합니다.
일반적으로 Python 라이브러리는 머신, 플랫폼 또는 경우에 따라 Python 버전 간에 단순히 복사할 수 없다는 점에 유의해야 합니다. 이 제안은 그러한 사실을 바꾸지 않으며, 스크립트와 해당 __pypackages__를 함께 묶는 것이 애플리케이션을 배포하는 메커니즘이라고 가정하고 싶을 수 있지만, 이는 명시적으로 이 제안의 목표가 아님을 밝힙니다. 개발자는 자신의 코드 이식성에 대해 계속 책임을 집니다.
근거
sys.path는 런타임에 조작할 수 있지만, 기본값은 사용자와 도구가 동의할 수 있는 공통 기준선을 설정하므로 중요합니다. 현재 기본값에는 “현재 프로젝트에만 비공개인” 것으로 볼 수 있는 위치가 포함되어 있지 않지만, 이는 유용한 개념입니다.
이는 Javascript 커뮤니티에서 널리 사용되는 npm node_modules 디렉터리와 유사하며, 해당 생태계에 익숙한 개발자가 Python에도 종종 요청하는 기능입니다.
사양
이 PEP는 시작 시 sys.path를 계산하는 과정에 새로운 단계를 추가할 것을 제안합니다.
대화형 인터프리터가 시작될 때 현재 작업 디렉터리에서 __pypackages__ 디렉터리가 발견되면, 현재 작업 디렉터리 항목 다음이면서 시스템 site-packages 바로 앞에 해당 디렉터리를 sys.path에 포함합니다.
인터프리터가 스크립트를 실행할 때 Python은 스크립트와 동일한 디렉터리에서 __pypackages__를 찾습니다. 해당 디렉터리가 발견되면(내부에 현재 Python 버전 디렉터리도 함께 있는 경우) 이를 사용하고, 그렇지 않으면 Python은 현재와 동일하게 동작합니다.
이 동작은 현재 작업 디렉터리 또는 스크립트 디렉터리를 sys.path에 추가하는 기존 메커니즘과 정확히 동일하게 작동해야 합니다. 예를 들어 -P 옵션이나 PYTHONSAFEPATH 환경 변수가 설정되어 있으면 __pypackages__는 무시됩니다.
인식되려면 __pypackages__ 디렉터리가 sysconfig 모듈의 새로운 localpackages 스킴에 따라 구성되어 있어야 합니다. 구체적으로는 purelib 디렉터리와 platlib 디렉터리가 모두 있어야 하며, 다음 코드를 사용하여 해당 디렉터리의 위치를 결정합니다.:
scheme = "localpackages"
purelib = sysconfig.get_path("purelib", scheme, vars={"base": "__pypackages__", "platbase": "__pypackages__"})
platlib = sysconfig.get_path("platlib", scheme, vars={"base": "__pypackages__", "platbase": "__pypackages__"})
이 두 위치는 sys.path에 추가되며, __pypackages__ 디렉터리의 다른 디렉터리나 파일은 조용히 무시됩니다. 경로는 파이썬 버전을 기준으로 합니다.
Note
별도의 새로운 API를 두는 선택지가 가능하며, 이는 issue #3013에 문서화되어 있습니다.
예시
다음은 예시 프로젝트 디렉터리 구조와 Python 실행 파일 및 모든 스크립트가 작동하는 다양한 방식을 보여 줍니다. 이 예시는 Unix 계열 시스템을 위한 것이며, Windows에서는 하위 디렉터리가 달라집니다.
foo
__pypackages__
lib
python3.10
site-packages
bottle
myscript.py
/> python foo/myscript.py
sys.path[0] == 'foo'
sys.path[1] == 'foo/__pypackages__/lib/python3.10/site-packages/'
cd foo
foo> /usr/bin/ansible
#! /usr/bin/env python3
foo> python /usr/bin/ansible
foo> python myscript.py
foo> python
sys.path[0] == '.'
sys.path[1] == './__pypackages__/lib/python3.10/site-packages'
foo> python -m bottle
foo라는 프로젝트 디렉터리가 있고 그 안에 __pypackages__가 있습니다. 해당 __pypackages__/lib/python3.10/site-packages/에 bottle을 설치했으며, 프로젝트 디렉터리 안에 myscript.py 파일이 있습니다. 해당 위치에 bottle을 설치하기 위해 평소 사용하는 도구를 사용했습니다.
스크립트를 호출하면 Python은 스크립트가 위치한 디렉터리 [1], 즉 /usr/bin 안에서 __pypackages__를 찾으려고 합니다. foo 디렉터리 안에서 /usr/bin/ansible을 실행하는 마지막 예시에서도 동일하게 작동합니다. 두 경우 모두 현재 작업 디렉터리에 있는 __pypackages__를 사용하지 않습니다.
마찬가지로 첫 번째 예시에서 myscript.py를 호출하면 foo 디렉터리에 있던 __pypackages__ 디렉터리를 사용합니다.
foo 디렉터리 안으로 들어가 Python 실행 파일(인터프리터)을 시작하면 현재 작업 디렉터리 안의 __pypackages__ 디렉터리를 찾아 sys.path에서 사용합니다. -m을 사용하여 모듈을 사용하려고 해도 동일하게 작동합니다. 이 예시에서는 bottle 모듈이 __pypackages__ 디렉터리 안에서 발견됩니다.
위의 두 예시만 현재 작업 디렉터리의 __pypackages__를 사용하는 경우입니다.
또 다른 예시 상황에서 Python 클래스의 강사는 “오늘은 Twisted를 사용하는 방법을 배워 봅시다! 시작하려면 예시 프로젝트를 체크아웃하고, 해당 디렉터리로 이동한 다음, 주어진 명령을 실행하여 Twisted를 설치하십시오.”라고 말할 수 있습니다.
그러면 Twisted가 python3과 별도의 디렉터리에 설치됩니다. 설치가 기본적으로 로컬에서 이루어지므로 가상 환경, 시스템 전체 설치와 사용자 설치의 차이 등을 논의할 필요가 없습니다. 그러면 강사는 활성화 단계 등을 거치지 않고 그들에게 python3을 사용하라고 계속 말할 수 있습니다.
가상 환경과의 관계
본질적으로 이 제안은 sys.path의 기본값 계산을 수정하는 것일 뿐이며, 가상 환경 메커니즘과는 전혀 관련이 없습니다. 그러나 __pypackages__는 격리 기능을 제공하는 것으로 볼 수 있으며, 그런 의미에서 가상 환경과 “경쟁”합니다.
그러나 중요한 차이점이 있습니다.
- 가상 환경은 시스템 환경과 격리되는 반면,
__pypackages__는 시스템 환경에 단순히 추가됩니다.- 가상 환경에는 바이너리, C 헤더 파일 등을 위한 디렉터리가 포함된 완전한 “설치 체계”가 있는 반면,
__pypackages__는 Python 라이브러리 코드만을 위한 것입니다.- 가상 환경은 “활성화”했을 때 가장 원활하게 작동합니다. 이 제안에는 활성화가 필요하지 않습니다.
이 제안은 가상 환경과 경쟁하는 것이 아니라, 가상 환경과 독립적인 것으로 보아야 합니다. 기껏해야 현재 가상 환경으로만 제공되는 일부 사용 사례도 __pypackages__로 제공할 수 있으며, 어쩌면 더 잘 제공할 수도 있습니다.
__pypackages__에 설치된 라이브러리는 가상 환경에서 표시된다는 점에 유의해야 합니다. 이는 가상 환경의 격리를 사실상 깨뜨리지만, 원칙적으로는 현재 디렉터리가 sys.path에 존재하는 것(또는 PYTHONPATH 환경 변수와 같은 메커니즘)과 다르지 않습니다. 유일한 차이는 정도에 있으며, 사람들이 __pypackages__에 패키지를 더 흔히 설치할 것으로 예상된다는 점입니다. 대안은 가상 환경을 명시적으로 감지하여 해당 경우 __pypackages__을 비활성화하는 것이지만, 그렇게 하면 종속성을 함께 포함한 스크립트가 손상됩니다. PEP 작성자들은 가상 환경을 사용하는 개발자라면 이 문제를 이해하고 문제를 예측하여 피할 만큼 충분한 경험이 있어야 한다고 생각합니다.
보안 고려 사항
이론적으로는 __pypackages__디렉터리에 표준 라이브러리 모듈이나 설치된 타사 라이브러리를 재정의하는 라이브러리를 추가할 수 있습니다. 스크립트와 연결된 __pypackages__의 경우에는 중대한 문제가 아니라고 가정합니다. 스크립트 자체에 쓸 수 있는 능력도 없는 한 누구도 __pypackages__에 쓸 수 있을 가능성은 낮기 때문입니다.
현재 작업 디렉터리에 있는 __pypackages__디렉터리의 경우 인터프리터가 대화형으로 실행되는 방식에 영향을 받을 수 있습니다. 그러나 이는 현재 디렉터리에 누군가 math.py 모듈을 두는 기존 문제와 크게 다르지 않으며, (그 경우와 마찬가지로) 사용자에게 혼란을 일으킬 수는 있지만 새로운 보안상의 영향을 초래하지는 않습니다.
스크립트를 실행할 때 현재 작업 디렉터리에 있는 모든 __pypackages__디렉터리는 무시됩니다. 이는 Python이 현재 작업 디렉터리를 sys.path에 추가할 때 사용하는 방식과 동일하며, 현재 디렉터리의 파일을 수정하여 스크립트의 동작을 변경할 수 없도록 보장합니다.
또한 __pypackages__ 디렉터리는 현재 디렉터리(또는 스크립트 디렉터리)에서만 인식됩니다. 인터프리터는 상위 디렉터리에서 __pypackages__를 검색하지 않습니다. 그렇게 하면 상위 디렉터리의 디렉터리 권한이 서로 다를 때 보안 문제가 발생할 위험이 생깁니다. 특히 bin 디렉터리 또는 __pypackages__ (sysconfig 용어로 scripts 위치)에 있는 스크립트는 __pypackages__에 설치된 라이브러리에 특별히 접근할 수 없습니다. 실행 가능한 스크립트를 bin 디렉터리에 넣는 것은 이 제안에서 지원되지 않습니다.
이 내용을 가르치는 방법
이 제안의 본래 동기는 초보자에게 Python을 더 쉽게 가르칠 수 있도록 하는 것이었습니다. 이를 위해서는 설명하기 쉽고 사용하기 간단해야 합니다.
가장 기본적인 수준에서 이는 스크립트 디렉터리를 sys.path에 추가하는 기존 메커니즘과 유사하며, 비슷한 방식으로 가르칠 수 있습니다. 그러나 “경량 격리”라는 의도된 용도를 위해서는 “__pypackages__ 디렉터리에 넣는 것은 스크립트 전용입니다”라는 관점으로 가르치게 될 가능성이 높습니다. PEP 작성자들의 경험에 따르면 이는 현재의 대안인 가상 환경 도입보다 훨씬 쉽게 가르칠 수 있습니다.
도구에 미치는 영향
이 기능은 새 디렉터리에 타사 라이브러리를 설치하는 용도로 사용될 예정이므로, 도구, 특히 설치 프로그램이 __pypackages__을 관리하는 방법을 이해하는 것이 중요합니다.
도구가 전용 “pypackages” 설치 모드를 도입하여 모든 경우에 예상된 레이아웃과 일치하도록 보장되기를 바랍니다. 그러나 __pypackages__ 레이아웃을 가장 잘 지원하는 방법에 대한 문제는 궁극적으로 개별 도구 유지 관리자가 검토하고 결정할 사항으로 남겨집니다.
실제로 Python 코드를 실행하지 않고 패키지를 찾는 도구(IDE, 린터, 타입 검사기 등)는 __pypackages__을 인식하도록 업데이트해야 합니다. 이러한 업데이트가 없으면 __pypackages__ 디렉터리는 런타임에 sys.path에 현재 추가되는 디렉터리와 유사하게 작동합니다(즉, 도구가 이를 무시할 가능성이 높습니다).
하위 호환성
디렉터리 이름 __pypackages__은 일반적으로 사용될 가능성이 낮기 때문에 선택되었습니다. 해당 이름을 자체적인 목적으로 사용하기로 선택한 사용자에게 영향을 미치게 된다는 것은 사실이지만, 이 PEP가 작성될 당시에는 비교적 위험이 낮은 것으로 여겨졌습니다.
안타깝게도 이 PEP가 논의되는 동안 여러 도구가 여기에서 제안된 내용의 변형을 구현하기로 선택했으며, 이러한 변형이 모두 PEP의 최종 형태와 호환되는 것은 아닙니다. 그 결과 충돌 위험은 이제 원래 예상했던 것보다 높아졌습니다.
원래 __pypackages__가 그랬던 것처럼 드문 다른 이름을 선택하여 이를 완화할 수는 있습니다. 그러나 현실적으로 호환성 문제는 제안의 변경 사항을 추적하려는 노력을 하지 않고 초안 제안을 구현하려고 한 사람들이 초래한 단순한 결과로 볼 수 있습니다. 따라서 __pypackages__이름을 유지하고, 호환성 문제를 해결할 책임을 초안 버전을 구현한 도구에 맡기는 것이 합리적으로 보입니다.
다른 Python 구현에 미치는 영향
다른 Python 구현은 인터프리터 부트스트랩의 새로운 동작을 재현해야 하며, 여기에는 __pypackages__디렉터리를 찾고 해당 디렉터리가 존재하는 경우 사이트 패키지 바로 앞에 sys.path에 추가하는 작업이 포함됩니다. 이는 다른 Python 변경 사항과 다르지 않습니다.
참조 구현
여기에는 Cpython 및 PyPy에서 구현을 활성화하는 작은 스크립트가 있습니다.
기각된 아이디어
__pylocal__이나python_modules같은 대안 이름들입니다. 결국 이름은 임의적이며, 선택된 이름으로도 충분합니다.- 가상 환경의 추가 기능들입니다. 이 제안은 가상 환경을 대체하는 것이 아니므로, 그러한 기능들은 범위 밖입니다.
__pypackages__를 찾기 위해 부모 디렉터리를 스캔하지 않을 것입니다.~/bin/디렉터리 안의 스크립트를 실행하고자 한다면,__pypackages__디렉터리는~/bin/디렉터리 안에 있어야 합니다.__pypackages__에 대해(인터프리터를 위해서든 스크립트를 위해서든) 그러한 스캔을 수행하면 보안상 문제가 발생하고 시작 시간도 늘어날 것입니다.__pypackages__에 예기치 않은 파일이나 디렉터리가 있으면 오류를 발생시킵니다. 이는 특히pip install --prefix와 같은 과도기적 접근 방식이__pypackages__에 추가 파일을 만들 수 있다는 점에서 지나치게 엄격하다고 간주됩니다.- 다른
sysconfig스킴을 사용하거나, 전용pypackages스킴을 사용하는 방식입니다. 이론적으로는 매력적이지만, 도구들이 명시적 지원을 구현하기 전까지는__pypackages__에 바로 설치할 수 있는 방법이 없기 때문에 전환을 더 어렵게 만듭니다. 그리고 PEP 저자들은 그러한 지원이 추가되기를 바라고 또 그렇게 될 것이라 가정하지만, 제안이 사용 가능하려면 그러한 지원에 의존해야 한다는 점은 용납할 수 없는 위험으로 보입니다.
Copyright
This document has been placed in the public domain.