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

Python 개선 제안 한국어 번역

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__ 디렉터리가 발견되면, 해당 디렉터리는 sys.path에서 현재 작업 디렉터리 항목 뒤, 시스템 사이트 패키지(site-packages) 바로 앞에 포함됩니다.

인터프리터가 스크립트를 실행할 때 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__ 디렉터리 안의 다른 디렉터리나 파일은 조용히 무시됩니다. 경로는 Python 버전을 기준으로 정해집니다.

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 작성자는 그러한 지원이 추가되기를 바라고 또 추가될 것이라고 가정하지만, 제안을 사용할 수 있는지가 그러한 지원에 의존하게 만드는 것은 받아들일 수 없는 위험으로 보입니다.