[Final] PEP 723 - Inline script metadata
원문 링크: PEP 723 - Inline script metadata
상태: Final 유형: Standards Track 작성일: 04-Aug-2023
PEP 723은 단일 파일 Python 스크립트에 메타데이터를 포함하는 표준 형식을 정의하여 런처(launcher), IDE 및 기타 외부 도구와 같은 도구가 스크립트와 상호 작용하는 데 도움을 주기 위한 Python Enhancement Proposal (PEP)입니다.
요약 (Abstract)
이 PEP는 단일 파일 Python 스크립트에 포함될 수 있는 메타데이터 형식을 지정하여, 이러한 스크립트와 상호 작용해야 할 수 있는 런처, IDE 및 기타 외부 도구를 지원합니다.
동기 (Motivation)
Python은 스크립팅 언어로 흔히 사용되며, 셸 스크립트나 배치 파일 등을 대체하는 (더 나은) 대안으로 활용됩니다. Python 코드가 스크립트 형태로 구성될 때, 일반적으로 단일 파일로 저장되며 다른 로컬 코드의 가용성을 기대하지 않습니다. 이 때문에 이메일, 스크립트 URL, 또는 채팅 창과 같은 텍스트 기반 수단을 통해 쉽게 공유될 수 있습니다. 이러한 방식으로 구성된 코드는 영원히 단일 파일로 존재하며, 자체 디렉토리와 pyproject.toml
파일을 가진 완전한 프로젝트로 발전하지 않을 수도 있습니다.
이러한 접근 방식에서 사용자들이 겪는 문제는, 스크립트를 실행하는 도구를 위한 메타데이터를 정의하는 표준 메커니즘이 없다는 것입니다. 예를 들어, 스크립트를 실행하는 도구는 필요한 종속성(dependencies)이나 지원되는 Python 버전(requires-python)을 알아야 할 수 있습니다.
현재 이 문제를 해결하는 표준 도구는 없으며, 이 PEP는 그러한 도구를 정의하려 시도하지 않습니다. 그러나 이 문제를 해결하는 모든 도구는 스크립트의 런타임 요구 사항을 알아야 할 것입니다. 이러한 메타데이터를 저장하기 위한 표준 형식을 정의함으로써, 기존 도구뿐만 아니라 미래의 모든 도구도 사용자가 스크립트에 도구별 메타데이터를 포함할 필요 없이 해당 정보를 얻을 수 있게 될 것입니다.
이론적 근거 (Rationale)
이 PEP는 메타데이터를 외부 파일이 아닌 스크립트 자체 내에 포함하기 위한 메커니즘을 정의합니다.
메타데이터 형식은 Python 프로젝트 디렉토리의 pyproject.toml
파일에 있는 데이터 레이아웃과 유사하게 설계되어, Python 프로젝트 작성 경험이 있는 사용자들에게 익숙한 경험을 제공합니다. 유사한 형식을 사용함으로써, 최근 패키징 설문조사에서 사용자들이 흔히 표현한 불만이었던 패키징 도구 간의 불필요한 불일치를 피할 수 있습니다.
이 PEP가 지원하고자 하는 몇 가지 사용 사례는 다음과 같습니다.
- 스크립트를 실행할 수 있는 사용자 대면 CLI (Command Line Interface). 예를 들어 Hatch를 사용한다면,
hatch run /path/to/script.py [args]
와 같이 간단하게 인터페이스를 사용할 수 있으며 Hatch가 해당 스크립트의 환경을 관리합니다. 이러한 도구는 비-Windows 시스템에서#!/usr/bin/env hatch run .
와 같은 셰뱅(shebang) 라인으로 사용될 수 있습니다. - 디렉토리 유형 프로젝트로 전환하려는 스크립트. 사용자는 로컬 또는 원격 REPL 환경에서 빠르게 프로토타입을 만들다가, 아이디어가 성공하면 더 정식적인 프로젝트 레이아웃으로 전환하기로 결정할 수 있습니다. 스크립트 내에서 종속성을 정의할 수 있으면 완전히 재현 가능한 버그 보고서(bug reports)를 만드는 데 매우 유용할 것입니다.
- 수동 종속성 관리(manual dependency management)를 피하고자 하는 사용자. 예를 들어, 종속성을 추가/제거하는 명령을 가진 패키지 관리자나, 새로운 버전 또는 CVE에 대응하여 CI에서 종속성 업데이트 자동화를 트리거하는 경우입니다.
사양 (Specification)
이 PEP는 reStructuredText Directives에서 영감을 받은 메타데이터 주석 블록 형식을 정의합니다.
모든 Python 스크립트에는 최상위 주석 블록이 있을 수 있으며, 이는 # /// TYPE
라인으로 시작해야 합니다. 여기서 TYPE
은 내용 처리 방법을 결정합니다. 즉, #
하나, 공백 하나, 슬래시 세 개, 공백 하나, 그리고 메타데이터 유형으로 구성됩니다. 블록은 # ///
라인으로 끝나야 합니다. 즉, #
하나, 공백 하나, 슬래시 세 개로 구성됩니다. TYPE
은 ASCII 문자, 숫자 및 하이픈으로만 구성되어야 합니다.
이 두 라인( # /// TYPE
과 ` # ///) 사이의 모든 라인은
#으로 시작하는 주석이어야 합니다.
# 뒤에 문자가 있는 경우, 첫 문자는 공백이어야 합니다. 포함된 내용은 각 라인의 두 번째 문자가 공백인 경우 첫 두 문자를 제거하고, 그렇지 않은 경우 (즉, 라인이
#` 하나로만 구성된 경우) 첫 문자를 제거하여 형성됩니다.
종료 라인 ` # ///`의 우선순위는 다음 라인이 위에서 설명한 유효한 포함된 내용 라인이 아닐 때 주어집니다. 예를 들어, 다음은 단일의 완전히 유효한 블록입니다.
# /// some-toml
# embedded-csharp = """
# /// <summary>
# /// text
# ///
# /// </summary>
# public class MyClass { }
# """
# ///
시작 라인은 다른 시작 라인과 해당 종료 라인 사이에 배치되어서는 안 됩니다. 이러한 경우 도구는 오류를 발생시킬 수 있습니다. 닫히지 않은 블록은 무시되어야 합니다.
동일한 TYPE
의 주석 블록이 여러 개 정의된 경우, 도구는 오류를 발생시켜야 합니다.
포함된 메타데이터를 읽는 도구는 표준 Python 인코딩 선언을 준수할 수 있습니다. 그렇게 하지 않기로 선택한 경우, 파일을 UTF-8로 처리해야 합니다.
메타데이터를 구문 분석하는 데 사용할 수 있는 표준 정규 표현식은 다음과 같습니다.
(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$
텍스트 사양과 정규 표현식 사이에 불일치가 있는 경우, 텍스트 사양이 우선합니다.
도구는 이 PEP 또는 향후 PEP에 의해 표준화되지 않은 유형의 메타데이터 블록에서 읽어서는 안 됩니다.
script
유형 (script type)
첫 번째 메타데이터 블록 유형은 script
로 명명되며 스크립트 메타데이터 (종속성 데이터 및 도구 구성)를 포함합니다.
이 문서에는 최상위 필드 dependencies
및 requires-python
이 포함될 수 있으며, 선택적으로 [tool]
테이블이 포함될 수 있습니다.
[tool]
테이블은 모든 도구 (스크립트 러너 또는 기타)가 동작을 구성하는 데 사용될 수 있습니다. 이는 pyproject.toml
의 tool
테이블과 동일한 의미를 가집니다.
최상위 필드는 다음과 같습니다.
dependencies
: 스크립트의 런타임 종속성을 지정하는 문자열 목록입니다. 각 항목은 유효한 PEP 508 종속성이어야 합니다.requires-python
: 스크립트가 호환되는 Python 버전(들)을 지정하는 문자열입니다. 이 필드의 값은 유효한 버전 지정자(version specifier)여야 합니다.
스크립트 러너는 지정된 종속성을 제공할 수 없는 경우 오류를 발생시켜야 합니다. 스크립트 러너는 지정된 requires-python
을 만족하는 Python 버전을 제공할 수 없는 경우 오류를 발생시켜야 합니다.
예시 (Example)
포함된 메타데이터가 있는 스크립트의 예시는 다음과 같습니다.
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
역방향 호환성 (Backwards Compatibility)
작성 시점에 # /// script
블록 주석 시작 구문은 GitHub의 어떤 Python 파일에도 나타나지 않습니다. 따라서 이 PEP로 인해 기존 스크립트가 손상될 위험은 거의 없습니다.
보안 영향 (Security Implications)
포함된 메타데이터를 포함하는 스크립트가 종속성을 자동으로 설치하는 도구를 사용하여 실행되는 경우, 임의의 코드가 다운로드되어 사용자 환경에 설치될 수 있습니다.
여기서 위험은 스크립트를 실행하는 데 사용되는 도구의 기능 중 일부이며, 따라서 도구 자체에서 이미 해결되어야 합니다. 이 PEP에 의해 도입되는 유일한 추가 위험은 포함된 메타데이터가 있는 신뢰할 수 없는 스크립트가 실행될 때 잠재적으로 악의적인 종속성 또는 전이적 종속성이 설치될 수 있다는 것입니다.
이 위험은 코드를 실행하기 전에 검토하는 일반적인 좋은 관행으로 해결됩니다. 또한, 도구는 이 위험을 완화하기 위해 잠금(locking) 기능을 제공할 수 있습니다.
교육 방법 (How to Teach This)
스크립트에 메타데이터를 포함하려면, # /// script
라인으로 시작하고 # ///
라인으로 끝나는 주석 블록을 정의합니다. 이 두 라인 사이의 모든 라인은 주석이어야 하며, 전체 내용은 첫 두 문자를 제거하여 파생됩니다.
# /// script
# dependencies = [
# "requests<3",
# "rich",
# ]
# requires-python = ">=3.11"
# ///
허용되는 필드는 다음 표에 설명되어 있습니다.
필드 (Field) | 설명 (Description) | 도구 동작 (Tool behavior) |
---|---|---|
dependencies |
스크립트의 런타임 종속성을 지정하는 문자열 목록입니다. 각 항목은 유효한 PEP 508 종속성이어야 합니다. | 도구는 지정된 종속성을 제공할 수 없는 경우 오류를 발생시킵니다. |
requires-python |
스크립트가 호환되는 Python 버전(들)을 지정하는 문자열입니다. 이 필드의 값은 유효한 버전 지정자여야 합니다. | 도구는 제약 조건을 만족하는 Python 버전을 실행할 수 없는 경우 오류를 발생시킬 수 있습니다. |
또한, [tool]
테이블이 허용됩니다. 허용되는 내용에 대한 세부 정보는 pyproject.toml
에서 허용되는 내용과 유사하지만, 정확한 정보는 해당 도구의 문서에 포함되어야 합니다.
포함된 메타데이터에 따라 도구의 동작이 변경될지 여부는 개별 도구에 달려 있습니다. 예를 들어, 모든 스크립트 러너가 requires-python
필드에 정의된 특정 Python 버전에 대한 환경을 제공할 수는 없을 수 있습니다.
tool
테이블은 모든 도구 (스크립트 러너 또는 기타)가 동작을 구성하는 데 사용될 수 있습니다.
권장 사항 (Recommendations)
다양한 Python 버전을 관리하는 도구는 스크립트의 requires-python
메타데이터가 정의되어 있는 경우, 호환되는 사용 가능한 Python 버전 중 가장 높은 버전을 사용하려고 시도해야 합니다.
도구의 지지 (Tooling buy-in)
다음은 이 PEP에 대한 지지를 표명했거나, 수락될 경우 지원을 구현하기로 약속한 도구 목록입니다.
- Pantsbuild 및 Pex: 종속성을 정의하는 모든 방식과 스크립트에서 패키지를 빌드하고 도구 구성을 포함하는 것과 같은 이 PEP가 유효한 사용 사례로 간주하는 기능에 대한 지지를 표명했습니다.
- Mypy 및 Ruff: 사용자에게 기존의 고충을 해결할 수 있는 도구 구성 포함에 대한 강력한 지지를 표명했습니다.
- Hatch: (이 PEP의 저자) 이 PEP의 모든 측면에 대한 지지를 표명했으며, 특히 구성된 Python 버전으로 스크립트를 실행하는 것을 지원하는 첫 번째 도구 중 하나가 될 것입니다.
거부된 아이디어 (Rejected Ideas)
왜 requirements.txt
와 유사한 주석 블록을 사용하지 않는가? (Why not use a comment block resembling requirements.txt?)
이 PEP는 Python 코드가 단일 파일 스크립트로 존재할 수 있는 다양한 유형의 사용자를 고려합니다.
- 비프로그래머: 특정 작업을 달성하기 위해 Python을 스크립팅 언어로 사용하는 사람들. 이들은 셰뱅(shebang) 라인이나
PATH
환경 변수와 같은 운영 체제 개념에 익숙하지 않을 가능성이 높습니다.- 예시: 직장에서 효율성을 높이거나 지루함을 줄이기 위해 스크립트를 작성하려는 일반인.
- 산업 또는 학계에서 데이터 과학이나 머신러닝을 하는 사람. 이들은 프로그래밍 지식이 제한적이지만, StackOverflow나 프로그래밍 관련 블로그에서 학습하며, Git(Hub), Jupyter, HuggingFace 등과 같은 지식과 코드를 공유하는 커뮤니티의 일원일 가능성이 점점 높아지고 있습니다.
- 운영 체제를 관리하는 비프로그래머: 예를 들어 시스템 관리자(sysadmin). 이들은
PATH
를 설정할 수 있지만, 가상 환경(virtual environments)과 같은 Python 개념에 익숙하지 않을 가능성이 높습니다. - 운영 체제/인프라를 관리하는 프로그래머: 예를 들어 SRE. 이들은 가상 환경과 같은 Python 개념에는 익숙하지 않을 가능성이 높지만, Git에는 익숙할 가능성이 높으며 Python 스크립트 및 Kubernetes 설정과 같은 인프라 관리에 필요한 모든 것을 버전 관리하는 데 Git을 가장 자주 사용합니다.
- 주로 자신을 위해 스크립트를 작성하는 프로그래머: 이들은 시간이 지남에 따라 작업 흐름을 자동화하는 데 사용하는 다양한 언어의 많은 스크립트를 축적하며, 종종 단일 디렉토리에 저장하고 잠재적으로 영구성을 위해 버전 관리합니다. 비-Windows 사용자는 원하는 Python 실행 파일 또는 스크립트 러너를 가리키는 셰뱅 라인으로 각 Python 스크립트를 설정할 수 있습니다.
이 PEP는 제안된 TOML 기반 메타데이터 형식이 각 사용자 범주에 가장 적합하며, requirements.txt
와 유사한 블록 주석은 requirements.txt
에 익숙한 사용자(이는 소수의 사용자만을 대표함)에게만 접근 가능하다고 주장합니다.
평범한 작업 자동화 사용자나 데이터 과학자의 경우, 이미 아무런 맥락 없이 시작하며 TOML이나 requirements.txt
에 익숙하지 않을 가능성이 높습니다. 이 사용자들은 검색 엔진을 통해 온라인에서 찾은 코드 스니펫이나 채팅 봇 또는 직접적인 코드 완성 소프트웨어 형태의 AI에 크게 의존할 것입니다. pyproject.toml
에 저장된 종속성 정보와의 유사성은 비교적 빠르게 유용한 검색 결과를 제공할 것이며, pyproject.toml
형식과 스크립트 메타데이터 형식이 동일하지는 않지만, 이로 인해 발생하는 불일치는 의도된 사용자가 해결하기 어렵지 않을 것입니다.
또한, 이 사용자들은 형식 관련 특이 사항 및 구문 오류에 가장 취약합니다. TOML은 Python 표현식과 호환되는 할당 기능을 갖추고 엄격한 들여쓰기 규칙이 없는 잘 정의된 형식이며, 기존 온라인 검증기가 있습니다. 반면에 블록 주석 형식은 예를 들어 콜론을 잊어버리는 등의 이유로 쉽게 잘못 형성될 수 있으며, 검색 엔진으로 작동하지 않는 이유를 디버깅하는 것은 이러한 사용자에게 어려운 작업이 될 것입니다.
시스템 관리자 유형의 경우, 이전 설명의 사용자들처럼 TOML이나 requirements.txt
에 익숙하지 않을 가능성이 높습니다. 어떤 형식이든 문서를 읽어야 할 것입니다. 그들은 구조화된 데이터 형식에 익숙하므로 TOML을 더 편하게 느낄 것이며, 시스템에서 마법처럼 보이는 것이 덜할 것입니다. 또한, 시스템 유지보수를 위해 # /// script
는 시간이 지남에 따라 수많은 확장 기능이 포함될 수 있는 블록 주석보다 셸에서 검색하기 훨씬 쉬울 것입니다.
SRE 유형의 경우, GitLab Runner 또는 Cloud Native Buildpacks 구성과 같이 작업해야 할 다른 프로젝트에서 이미 TOML에 익숙할 가능성이 높습니다. 이 사용자들은 시스템의 보안을 책임지며, 종속성 버전을 업데이트하기 위해 PR(Pull Request)을 자동으로 열도록 보안 스캐너를 설정해 놓았을 가능성이 높습니다. Dependabot과 같은 자동화 도구는 블록 주석 형식에 대한 자체 사용자 지정 파서를 작성하는 것보다 기존 TOML 라이브러리를 훨씬 쉽게 사용할 수 있을 것입니다.
프로그래머 유형의 경우, 애플리케이션 작성을 경험했던 Python 프로그래머가 아니라면 requirements.txt
파일을 본 적이 없을 가능성보다 TOML에 익숙할 가능성이 더 높습니다. requirements
형식 경험이 있는 경우, 이는 적어도 생태계에 어느 정도 익숙하다는 것을 의미하며, 따라서 TOML이 무엇인지 안다고 가정하는 것이 안전합니다.
이 PEP가 이러한 사용자에게 주는 또 다른 이점은 Visual Studio Code와 같은 IDE가 이 기능에 대한 사용자 지정 로직을 각자 작성하는 것보다 훨씬 쉽게 TOML 구문 강조를 제공할 수 있다는 것입니다.
또한, 원래의 블록 주석 대안 형식(이중 #
)이 PEP 8의 권장 사항에 위배되었고, 그 결과 권장 사항을 존중하는 린터(linters) 및 IDE 자동 포맷터(auto-formatters)가 기본적으로 실패했기 때문에, 최종 제안은 명확한 시작 또는 끝 시퀀스 없이 단일 #
문자로 시작하는 표준 주석을 사용합니다.
기계용으로 의도되지 않은 것으로 보이는 일반 주석(예: 인코딩 선언)이 동작에 영향을 미치는 개념은 Python 사용자에게 익숙하지 않을 것이며, “명시적인 것이 암시적인 것보다 낫다(explicit is better than implicit)”는 근본 원칙에 정면으로 위배됩니다. 사용자가 그들에게 산문처럼 보이는 것을 입력하여 런타임 동작을 변경할 수 있습니다. 이 PEP는 그런 일이 발생할 가능성, 심지어 도구가 그렇게 설정되었을 때도(아마도 시스템 관리자에 의해) 사용자에게 불친절하다고 봅니다.
마지막으로, 그리고 결정적으로, PEP 722와 같은 이 PEP의 대안들은 지원되는 Python 버전 설정, 스크립트의 궁극적인 패키지 빌드, 그리고 기계가 사용자를 대신하여 메타데이터를 편집할 수 있는 능력과 같이 여기에 열거된 사용 사례를 만족시키지 못합니다. 이러한 기능에 대한 요청은 계속될 가능성이 매우 높으며, 미래에 또 다른 PEP가 그러한 메타데이터 포함을 허용할 수도 있습니다. 그 시점에서 동일한 것을 달성하는 여러 가지 방법이 생겨날 것이며, 이는 “한 가지 – 그리고 가급적이면 오직 한 가지 – 명확한 방법이 있어야 한다”는 우리의 근본 원칙에 위배됩니다.
왜 여러 줄 문자열을 사용하지 않는가? (Why not use a multi-line string?)
이 PEP의 이전 버전에서는 메타데이터를 다음과 같이 저장할 것을 제안했습니다.
__pyproject__ = """
...
"""
이 제안의 가장 중요한 문제는 포함된 TOML이 다음과 같은 방식으로 제한된다는 것입니다.
- TOML 내에서 여러 줄의 이중 따옴표 문자열을 사용할 수 없을 것입니다. 이는 문서를 포함하는 Python 문자열과 충돌하기 때문입니다.
- 많은 TOML 작성기는 스타일을 보존하지 않으며 잠재적으로 잘못 형성된 출력을 생성할 수 있습니다.
- Python 문자열에서 문자 이스케이프(escaping)가 작동하는 방식은 TOML 문자열에서 작동하는 방식과 상당히 다릅니다. raw 문자열을 강제하여 1대1 문자 매핑을 보존하는 것이 가능할 수 있지만, 이
r
접두사 요구 사항은 사용자에게 혼란을 줄 수 있습니다.
왜 코어 메타데이터 필드를 재사용하지 않는가? (Why not reuse core metadata fields?)
이 PEP의 이전 버전에서는 프로젝트를 설명하는 데 사용되는 기존 메타데이터 표준을 재사용할 것을 제안했습니다.
이 제안에는 두 가지 중요한 문제가 있습니다.
name
및version
필드는 필수이며, 이를 변경하려면 자체 PEP가 필요합니다.- 데이터를 재사용하는 것은 근본적으로 잘못된 사용입니다.
왜 특정 메타데이터 필드로 제한하지 않는가? (Why not limit to specific metadata fields?)
메타데이터를 dependencies
로만 제한한다면, Python 설치 관리를 지원하는 도구의 알려진 사용 사례를 막게 될 것입니다. 이는 사용자가 새로운 구문이나 표준 라이브러리 기능을 위해 특정 Python 버전을 대상으로 할 수 있도록 허용하는 것입니다.
왜 도구 구성을 제한하지 않는가? (Why not limit tool configuration?)
[tool]
테이블을 허용하지 않는다면, 사용자에게 이점을 줄 수 있는 알려진 기능을 막게 될 것입니다. 예를 들어:
- 스크립트 러너는 포함된 잠금 파일(lock file)에 대한 종속성 해결 데이터 주입을 지원할 수 있습니다 (이것은 Go의
gorun
이 할 수 있는 일입니다). - 스크립트 러너는 종속성에 대한 크로스 플랫폼 지원이 없거나 Nvidia 드라이버가 필요할 때와 같이 일반 사용자에게 설정이 너무 복잡한 상황에서 스크립트를 컨테이너에서 실행하도록 지시하는 구성을 지원할 수 있습니다. 이러한 상황은 사용자가 원하는 작업을 계속 진행할 수 있게 해주며, 그렇지 않으면 해당 지점에서 완전히 중단될 수도 있습니다.
- 도구는 단일 파일 스크립트를 패키지로 빌드하는 것과 같이 사용자의 개발 부담을 덜어주기 위한 기능을 실험하고 싶을 수 있습니다. 우리는 이미 단일 파일에서 휠(wheels) 및 소스 배포판(source distributions)을 빌드하는 도구가 존재한다는 피드백을 받았습니다.
Rust RFC의 메타데이터 포함 관련 저자는 작은 프로젝트를 관리하는 데 불필요한 마찰이 있다는 사용자 피드백을 바탕으로 그들도 이 부분을 적극적으로 검토하고 있다고 언급했습니다.
적어도 하나의 주요 빌드 시스템에서 이를 지원하겠다는 약속이 있었습니다.
왜 도구 동작을 제한하지 않는가? (Why not limit tool behavior?)
이 PEP의 이전 버전에서는 스크립트가 도구에 대한 유일한 입력이 아닐 때 스크립트 실행 도구가 아닌 도구는 동작을 수정해서는 안 된다고 제안했습니다. 예를 들어, 린터가 디렉토리 경로로 호출되는 경우, 임베디드 메타데이터가 없는 파일이 0개인 경우와 동일하게 동작해야 한다는 것입니다.
이는 도구 동작 혼란을 피하고 이 PEP를 지원하기 위한 다양한 기능 요청을 생성하는 것을 방지하기 위한 예방 조치였습니다. 그러나 논의 과정에서 도구 관리자들로부터 이것이 바람직하지 않고 사용자에게 혼란을 줄 수 있다는 피드백을 받았습니다. 또한, 이는 특정 상황에서 도구를 구성하는 데 보편적으로 더 쉬운 방법을 제공하고 기존 문제를 해결할 수 있게 할 수도 있습니다.
왜 pyproject.toml
로 Python 프로젝트를 설정하지 않는가? (Why not just set up a Python project with a pyproject.toml ?)
다시 한번, 여기서 핵심 문제는 이 제안의 대상 청중이 배포용이 아닌 스크립트를 작성하는 사람들이라는 것입니다. 때로는 스크립트가 “공유”될 수도 있지만, 이는 “배포”보다 훨씬 비공식적입니다. 일반적으로 스크립트 실행 방법에 대한 몇 가지 지침과 함께 이메일로 스크립트를 보내거나, GitHub gist 링크를 다른 사람에게 전달하는 것을 포함합니다.
이러한 사용자들에게 Python 패키징의 복잡성을 배우도록 기대하는 것은 복잡성이 크게 증가하는 것이며, 거의 확실하게 “Python은 스크립트에 너무 어렵다”는 인상을 줄 것입니다.
또한, 여기서 pyproject.toml
이 스크립트를 제자리에서 실행하기 위해 설계될 것이라는 기대가 있다면, 이는 현재 존재하지 않는 표준의 새로운 기능입니다. 최소한, 휠(wheels)로 배포되지 않을 프로젝트에 pyproject.toml
을 사용하는 것에 대한 현재 Discourse 논의가 해결될 때까지 이것은 합리적인 제안이 아닙니다. 그리고 그때조차도, “gist나 이메일로 스크립트를 다른 사람에게 보내는” 사용 사례를 해결하지 못합니다.
왜 import
문에서 요구 사항을 추론하지 않는가? (Why not infer the requirements from import statements?)
소스 파일의 import
문을 자동으로 인식하여 요구 사항 목록으로 변환하는 아이디어가 있었습니다.
그러나 이는 여러 가지 이유로 실행 불가능합니다. 첫째, 다른 언어로 작성된 도구에 의해서도 모든 Python 버전에서 구문을 쉽게 구문 분석할 수 있도록 유지해야 할 필요성에 대한 위의 요점들이 여기에 동일하게 적용됩니다.
둘째, PyPI 및 Simple Repository API를 준수하는 다른 패키지 저장소는 가져온 모듈 이름에서 패키지 이름을 확인하는 메커니즘을 제공하지 않습니다 (이 관련 논의 참조).
셋째, 저장소가 이 정보를 제공하더라도 동일한 import
이름이 PyPI의 여러 패키지에 해당할 수 있습니다. 동일한 import
이름을 제공하는 여러 프로젝트가 있는 경우에만 어떤 패키지가 원하는 것인지 모호성을 해소할 필요가 있을 것이라고 반대할 수 있습니다. 그러나 이는 기존 프로젝트와 동일한 import
이름을 제공하는 패키지를 PyPI에 업로드함으로써 누구든지 의도치 않게 또는 악의적으로 작동하는 스크립트를 쉽게 손상시킬 수 있게 할 것입니다. 후보 중에서 인덱스에 가장 먼저 등록된 패키지가 선택되는 대안은 인기 있는 패키지가 기존의 모호한 패키지와 동일한 import
이름으로 개발될 경우 혼란스러울 것이며, 기존 패키지가 재사용될 가능성이 높은 충분히 일반적인 import
이름으로 의도적으로 업로드된 악성웨어인 경우 해로울 수도 있습니다.
관련 아이디어는 요구 사항을 블록에 모으는 대신 import
문에 주석으로 첨부하는 것으로, 다음과 같은 구문을 사용하는 것입니다.
import numpy as np # requires: numpy
import rich # requires: rich
이것은 여전히 구문 분석 어려움을 겪습니다. 또한, 여러 줄 import
의 경우 주석을 어디에 배치해야 할지 모호하며 보기 좋지 않을 수 있습니다.
from PyQt5.QtWidgets import (
QCheckBox, QComboBox, QDialog, QDialogButtonBox, QGridLayout,
QLabel, QSpinBox, QTextEdit
) # requires: PyQt5
더 나아가, 이 구문은 모든 상황에서 직관적으로 예상되는 대로 동작하지 않을 수 있습니다. 다음을 고려하십시오.
import platform
if platform.system() == "Windows":
import pywin32 # requires: pywin32
여기서 사용자의 의도는 패키지가 Windows에서만 필요하다는 것이지만, 스크립트 러너는 이를 이해할 수 없습니다 (requires: pywin32 ; sys_platform == 'win32'
와 같이 올바르게 작성해야 합니다).
(이 점에 대한 명확한 논의를 제공한 Jean Abou-Samra에게 감사드립니다)
왜 종속성을 위해 requirements
파일을 사용하지 않는가? (Why not use a requirements file for dependencies?)
요구 사항을 requirements
파일에 넣는 것은 PEP를 필요로 하지 않습니다. 지금 바로 그렇게 할 수 있으며, 실제로 많은 임시 솔루션이 이렇게 합니다. 그러나 표준이 없으면 스크립트의 종속성 데이터를 찾는 방법을 알 수 없습니다. 또한, requirements
파일 형식은 pip에만 해당되므로, 이에 의존하는 도구는 pip 구현 세부 사항에 의존하게 됩니다.
따라서 표준을 만들려면 두 가지가 필요합니다.
requirements
파일 형식을 대체할 표준화된 형식.- 주어진 스크립트에 대한
requirements
파일을 찾는 방법에 대한 표준.
첫 번째 항목은 상당한 작업입니다. 여러 차례 논의되었지만, 아직 아무도 실제로 시도하지 않았습니다. 가장 유력한 접근 방식은 현재 requirements
파일로 해결되는 개별 사용 사례에 대해 표준을 개발하는 것입니다. 여기서 한 가지 옵션은 이 PEP가 단순히 PEP 508 요구 사항을 한 줄에 하나씩 포함하는 텍스트 파일인 새 파일 형식을 정의하는 것입니다. 그러면 해당 파일을 찾는 방법에 대한 질문만 남습니다.
여기서 “명확한” 해결책은 스크립트와 동일한 이름으로 .reqs
확장자(또는 유사한 것)를 사용하여 파일을 명명하는 것과 같은 일을 하는 것입니다. 그러나 이것은 여전히 두 개의 파일을 필요로 하며, 현재는 단일 파일만 필요하고, 따라서 “더 나은 배치 파일” 모델(셸 스크립트 및 배치 파일은 일반적으로 자체 포함됨)과 일치하지 않습니다. 개발자가 두 파일을 함께 유지해야 한다는 것을 기억해야 하며, 이는 항상 가능하지 않을 수 있습니다. 예를 들어, 시스템 관리 정책은 특정 디렉토리의 모든 파일이 실행 가능해야 한다고 요구할 수 있습니다 (예를 들어 Linux 파일 시스템 표준은 /usr/bin
에 대해 이를 요구합니다). 그리고 스크립트를 공유하는 일부 방법(예: Github의 gist와 같은 텍스트 파일 공유 서비스 또는 기업 인트라넷에 게시)은 스크립트의 위치에서 관련 requirements
파일의 위치를 파생하는 것을 허용하지 않을 수 있습니다 (pipx와 같은 도구는 URL에서 직접 스크립트를 실행하는 것을 지원하므로, “스크립트 및 종속성 zip을 다운로드하고 압축 해제”는 적절한 요구 사항이 아닐 수 있습니다).
본질적으로, 여기서 문제는 형식이 스크립트 파일 자체에 종속성 데이터를 저장하는 것을 지원해야 한다는 명시적인 요구 사항이 있다는 것입니다. 그렇게 하지 않는 솔루션은 단순히 그 요구 사항을 무시하는 것입니다.
왜 (제한될 수 있는) Python 구문을 사용하지 않는가? (Why not use (possibly restricted) Python syntax?)
이는 일반적으로 다음과 같이 여러 특수 변수로 메타데이터를 저장하는 것을 포함합니다.
__requires_python__ = ">=3.11"
__dependencies__ = [
"requests",
"click",
]
이 제안의 가장 중요한 문제는 종속성 데이터의 모든 소비자가 Python 파서(parser)를 구현해야 한다는 것입니다. 구문이 제한되더라도 스크립트의 나머지는 전체 Python 구문을 사용할 것이며, 주변 코드와 독립적으로 성공적으로 구문 분석될 수 있는 구문을 정의하는 것은 극도로 어렵고 오류가 발생하기 쉽습니다.
더 나아가, Python의 구문은 모든 릴리스에서 변경됩니다. 종속성 데이터를 추출하는 데 Python 파서가 필요하다면, 파서는 스크립트가 작성된 Python 버전을 알아야 할 것이며, 여러 버전의 Python을 처리할 수 있는 파서를 갖는 일반 도구의 오버헤드는 지속 불가능합니다.
이 접근 방식에서는 새로운 확장이 추가될 때 많은 변수로 스크립트를 어지럽힐 가능성이 있습니다. 또한, 어떤 메타데이터 필드가 어떤 변수 이름에 해당하는지 직관적으로 파악하는 것은 사용자에게 혼란을 야기할 것입니다.
그러나 pip-run
유틸리티는 이 접근 방식(확장된 형태)을 구현하고 있습니다. pip-run
디자인에 대한 추가 논의는 프로젝트의 이슈 트래커에서 확인할 수 있습니다.
로컬 종속성은 어떻게 되는가? (What about local dependencies?)
이들은 특별한 메타데이터나 도구 없이, 단순히 sys.path
에 종속성의 위치를 추가함으로써 처리될 수 있습니다. 이 PEP는 이 경우에 필요하지 않습니다. 반면에 “로컬 종속성”이 로컬로 게시되는 실제 배포판인 경우, PEP 508 요구 사항으로 평소와 같이 지정할 수 있으며, 도구를 실행할 때 도구의 UI를 사용하여 로컬 패키지 인덱스를 지정할 수 있습니다.
주석 (Footnotes)
- 많은 사용자가 버전 관리되는 스크립트를 사용합니다. 예를 들어 언급된 SRE나 AWS CLI 또는 Calibre와 같이 특별한 유지보수가 필요한 프로젝트들입니다.
- 이 구문은 Python Markdown의 Blocks 확장 최종 해결책에서 직접 가져왔습니다.
저작권 (Copyright) 이 문서는 퍼블릭 도메인 또는 CC0-1.0-Universal 라이선스 중 더 관대한 라이선스에 따라 게시됩니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments