[Final] PEP 561 - Distributing and Packaging Type Information
원문 링크: PEP 561 - Distributing and Packaging Type Information
상태: Final 유형: Standards Track 작성일: 09-Sep-2017
PEP 561 – 타입 정보 배포 및 패키징
개요 (Abstract)
PEP 484는 점진적이고 쉽게 채택할 수 있는 목표를 가지고 Python에 타입 힌팅(Type Hinting)을 도입했습니다. 현재 타입 정보는 수동으로 배포되어야 합니다. 이 PEP는 기존 툴링(tooling)을 활용하여 최소한의 작업으로 타입 정보를 패키징하고 배포할 수 있는 표준화된 방법을 제공하며, 타입 체커(Type Checker)가 모듈을 해석하고 타입 검사를 위해 이 정보를 수집하는 순서를 정의합니다.
배경 (Rationale)
현재 패키지 작성자는 인라인(inline) 타입 정보가 포함된 코드를 배포하고자 합니다. 또한, 관리자는 최신 주석 구문(annotation syntax)을 사용하면서 Python 2 호환성을 유지하기 위해 스텁(stub) 파일을 배포하기를 원합니다. 그러나 타입 정보가 포함된 패키지를 배포하는 표준화된 방법이 없습니다. 또한, 개인적으로 스텁 파일을 제공하고자 한다면 MYPYPATH 또는 그에 상응하는 것을 설정하여 스텁 파일을 수동으로 지정하는 방법밖에 없었습니다. 만약 패키지를 공개적으로 배포할 수 있다면 typeshed에 추가할 수 있지만, 이는 확장성이 떨어지고 typeshed 관리자에게 부담이 됩니다. 또한, 스텁 파일의 버그 수정이 typeshed를 사용하는 도구의 릴리스에 종속됩니다.
PEP 484는 타입 정보 배포에 대한 간략한 섹션을 가지고 있습니다. 이 섹션에서는 스텁 파일을 배포하기 위해 shared/typehints/pythonX.Y/
사용을 권장합니다. 그러나 각 서드파티 라이브러리(third party library)에 스텁 파일 경로를 수동으로 추가하는 것은 확장성이 떨어집니다. 가장 간단한 접근 방식은 site-packages
를 MYPYPATH
에 추가하는 것이었지만, 이는 동적(dynamic)인 패키지(예: SQLAlchemy, Django)에서 타입 체커가 실패하는 원인이 됩니다.
용어 정의 (Definition of Terms)
“MAY”, “MUST”, “SHOULD”, “SHOULD NOT”의 정의는 RFC 2119에 기술된 대로 해석됩니다.
- “inline”: 타입이 PEP 526 및 PEP 3107 구문(
*.py
파일)을 사용하여 런타임 코드의 일부로 포함된 경우를 의미합니다. - “stubs”: 런타임 코드는 없고 타입 정보만 포함하는 파일(
*.pyi
파일)을 의미합니다. - “Distributions”: 릴리스를 발행하고 배포하는 데 사용되는 패키지화된 파일입니다 (PEP 426).
- “Module”: Python 런타임 코드 또는 스텁 타입 정보가 포함된 파일입니다.
- “Package”: Python 모듈의 네임스페이스를 제공하는 디렉터리 또는 디렉터리들입니다. (패키지와 배포(distribution)의 차이에 유의해야 합니다. 대부분의 배포는 하나의 패키지 이름을 따르지만, 일부 배포는 여러 패키지를 설치합니다.)
사양 (Specification)
패키지에서 타입 지원을 위한 여러 동기 및 방법이 있습니다. 이 PEP는 타입 사용자가 생성하고자 하는 세 가지 유형의 패키지를 인식합니다.
- 패키지 관리자가 타입 정보를 인라인으로 추가하고자 하는 경우.
- 패키지 관리자가 스텁 파일을 통해 타입 정보를 추가하고자 하는 경우.
- 서드파티 또는 패키지 관리자가 특정 패키지에 대한 스텁 파일을 공유하고자 하지만, 해당 관리자가 스텁 파일을 패키지 소스에 포함하고 싶지 않은 경우.
이 PEP는 위 세 가지 시나리오를 모두 지원하며, 패키징 및 배포에 간단하게 추가할 수 있도록 하는 것을 목표로 합니다.
이 사양의 두 가지 주요 부분은 패키징 사양과 모듈 타입 정보 해석을 위한 해석 순서입니다. 타입 체킹 사양은 PEP 484의 shared/typehints/pythonX.Y/
사양을 대체하기 위한 것입니다.
새로운 서드파티 스텁 라이브러리는 typeshed에 추가되는 대신, 이 PEP에서 제안하는 서드파티 패키징 방법을 통해 스텁을 배포해야 합니다 (SHOULD
). Typeshed는 계속 사용되지만, 관리자가 있는 경우 typeshed의 서드파티 스텁은 자체 패키지로 분리될 수 있습니다 (MAY
).
타입 정보 패키징 (Packaging Type Information)
타입 정보 패키징 및 배포를 최대한 간단하고 쉽게 만들기 위해, 패키징 및 배포는 기존 프레임워크를 통해 이루어집니다.
코드의 타입 검사를 지원하려는 패키지 관리자는 패키지에 py.typed
라는 마커 파일(marker file)을 추가해야 합니다 (MUST
). 이 마커는 재귀적으로 적용됩니다. 즉, 최상위 패키지가 이 파일을 포함하면 모든 하위 패키지(sub-packages)도 타입 검사를 지원해야 합니다 (MUST
). 이 파일을 패키지와 함께 설치하기 위해 관리자는 아래 예시와 같이 distutils
의 package_data
와 같은 기존 패키징 옵션을 사용할 수 있습니다.
Distutils 옵션 예시:
setup(
...,
package_data = {
'foopkg': ['py.typed'],
},
...,
)
네임스페이스 패키지(namespace packages, PEP 420 참조)의 경우, py.typed
파일은 충돌을 피하고 명확성을 위해 네임스페이스의 서브모듈(submodules)에 있어야 합니다.
이 PEP는 모듈 전용 배포(module-only distributions) 또는 네임스페이스 패키지 내의 단일 파일 모듈(single-file modules)의 일부로 타입 정보를 배포하는 것을 지원하지 않습니다. 단일 파일 모듈은 패키지로 리팩터링되고, 위에서 설명한 대로 해당 패키지가 타입 지정을 지원함을 나타내야 합니다.
스텁 전용 패키지 (Stub-only Packages)
모든 타입 정보가 포함된 스텁 파일을 배포하려는 패키지 관리자의 경우, *.pyi
스텁 파일이 해당 *.py
파일과 함께 있는 것이 선호됩니다. 그러나 스텁은 별도의 패키지에 넣어 개별적으로 배포될 수도 있습니다. 서드파티 또한 스텁 파일을 배포하고자 할 때 이 방법을 유용하게 사용할 수 있습니다. 스텁 패키지의 이름은 foopkg
라는 패키지에 대한 타입 스텁의 경우 foopkg-stubs
스키마를 따라야 합니다 (MUST
). 스텁 전용 패키지의 경우 -stubs
라는 이름 자체가 타입 정보의 소스임을 나타내기에 py.typed
마커를 추가할 필요가 없습니다.
스텁 파일을 배포하려는 서드파티는 패키지 관리자에게 패키지와 함께 배포하는 것에 대해 문의하는 것이 좋습니다. 관리자가 스텁 파일이나 인라인 타입 정보의 유지 보수 또는 패키징을 원하지 않는다면, 서드파티 스텁 전용 패키지를 생성할 수 있습니다.
또한, 스텁 전용 배포는 일반적인 의존성 데이터(dependency data)를 통해 런타임 패키지의 버전(들)을 명시함으로써 어떤 버전의 런타임 패키지를 지원하는지 나타내야 합니다 (SHOULD
). 예를 들어, 스텁 패키지 flyingcircus-stubs
는 distutils
기반 도구의 install_requires
또는 다른 패키징 도구의 동등한 기능을 통해 지원하는 런타임 flyingcircus
배포의 버전을 명시할 수 있습니다. pip 9.0에서는 flyingcircus-stubs
를 업데이트하면 flyingcircus
도 업데이트됩니다. pip 9.0에서는 --upgrade-strategy=only-if-needed
플래그를 사용할 수 있으며, pip 10.0에서는 이것이 기본 동작입니다.
네임스페이스 패키지(PEP 420 참조)의 경우, 스텁 전용 패키지는 루트 네임스페이스 패키지에만 -stubs
접미사를 사용해야 합니다. 모든 스텁 전용 네임스페이스 패키지는 __init__.pyi
파일을 생략해야 합니다. py.typed
마커 파일은 스텁 전용 패키지에는 필요하지 않지만, 인라인 타입이 있는 패키지와 유사하게 사용되는 경우 충돌을 피하고 명확성을 위해 네임스페이스의 서브모듈에 있어야 합니다.
예를 들어, pentagon
과 hexagon
이 shapes.polygons
네임스페이스 패키지 내에 설치되는 별도의 배포라면, 해당 타입 전용 배포는 다음과 같이 패키지를 구성해야 합니다.
shapes-stubs/
└── polygons/
└── pentagon/
└── __init__.pyi
shapes-stubs/
└── polygons/
└── hexagon/
└── __init__.pyi
타입 체커 모듈 해석 순서 (Type Checker Module Resolution Order)
이 PEP를 지원하는 타입 체커가 타입 정보가 포함된 모듈을 해석하는 순서는 다음과 같아야 합니다 (SHOULD
).
- 경로의 시작 부분에 수동으로 배치된 스텁 또는 Python 소스: 타입 체커는 사용자가 사용할 스텁을 완전히 제어하고, 패키지의 손상된 스텁/인라인 타입을 패치할 수 있도록 이를 제공해야 합니다 (
SHOULD
). mypy에서는$MYPYPATH
환경 변수를 사용하여 이를 수행할 수 있습니다. - 사용자 코드: 타입 체커가 실행하는 파일입니다.
- 스텁 패키지: 이 패키지는 설치된 인라인 패키지를 대체해야 합니다 (
SHOULD
).foopkg
패키지의 경우foopkg-stubs
에서 찾을 수 있습니다. py.typed
마커 파일이 있는 패키지: 설치된 패키지를 재정의하는 것이 없고, 해당 패키지가 타입 검사를 선택(opt into)하는 경우, 패키지와 함께 번들된 타입(.pyi
타입 스텁 파일이든.py
파일 내에 인라인으로 있든)이 사용되어야 합니다 (SHOULD
).- Typeshed (사용하는 경우): 표준 라이브러리(stdlib) 타입과 여러 서드파티 라이브러리를 제공합니다.
타입 체커가 3단계에서 원하는 모듈이 없는 스텁 전용 네임스페이스 패키지를 식별하면, 4단계/5단계로 계속 진행해야 합니다. 타입 체커는 __init__.pyi
파일의 부재를 통해 네임스페이스 패키지를 식별해야 합니다. 이는 다른 서브패키지가 인라인 방식과 스텁 전용 방식 중 독립적으로 선택할 수 있도록 합니다.
실행되는 Python 버전과 다른 Python 버전을 검사하는 타입 체커는 해당 Python 버전의 site-packages
/ dist-packages
에서 타입 정보를 찾아야 합니다 (MUST
). 이는 예를 들어 pythonX.Y -c 'import site; print(site.getsitepackages())'
와 같이 쿼리할 수 있습니다. 또한, 경로에 없는 경우 사용자가 특정 Python 바이너리를 지정할 수 있도록 타입 체커가 허용하는 것이 권장됩니다.
부분적인 스텁 패키지 (Partial Stub Packages)
많은 스텁 패키지는 특히 초기에 라이브러리의 타입 인터페이스 중 일부만 완성되어 있을 것입니다. 타입 검사 및 코드 편집기의 이점을 위해 패키지는 “부분적(partial)”일 수 있습니다. 이는 스텁 패키지에서 찾을 수 없는 모듈은 위 모듈 해석 순서의 4단계와 5단계, 즉 인라인 패키지 및 typeshed에서 찾아야 함을 의미합니다 (SHOULD
).
타입 체커는 스텁 패키지와 런타임 패키지 또는 typeshed 디렉터리를 병합해야 합니다. 이는 스텁 패키지를 해당 런타임 패키지 또는 typeshed 폴더와 동일한 디렉터리에 복사하고 병합된 디렉터리 구조를 타입 검사하는 것과 기능적으로 동일하다고 생각할 수 있습니다. 따라서 타입 체커는 *.pyi
파일을 *.py
파일보다 먼저 확인하는 일반적인 해석 순서를 유지해야 합니다 (MUST
).
스텁 패키지 배포가 부분적이라면 py.typed
파일에 partial\n
을 포함해야 합니다 (MUST
). 네임스페이스 패키지 내에서 배포되는 스텁 패키지의 경우(PEP 420 참조), py.typed
파일은 네임스페이스의 서브모듈에 있어야 합니다.
타입 체커는 여러 배포가 네임스페이스 패키지를 채울 수 있으므로 스텁 패키지 내의 네임스페이스 패키지를 불완전한 것으로 처리해야 합니다. 스텁 패키지 배포 내의 네임스페이스 패키지 내의 일반 패키지는 partial\n
이 포함된 py.typed
파일이 포함되지 않는 한 완전한 것으로 간주됩니다.
구현 (Implementation)
타입 지원을 나타내는 제안된 방식은 완전히 하위 호환되며, 패키지 툴링을 수정할 필요가 없습니다. 인라인 타입이 있는 샘플 패키지 [typed_package]와 [stub_package]가 제공됩니다. 설치된 패키지의 메타데이터를 읽고 타입이 지정되지 않았는지(not typed), 인라인 타입이 지정되었는지(inline typed), 또는 스텁 패키지인지 보고하는 샘플 패키지 체커 [pkg_checker]도 있습니다.
mypy 타입 체커는 mypy 문서에서 확인할 수 있는 PEP 561 검색을 구현했습니다.
[numpy-stubs]는 numpy
배포를 위한 실제 스텁 전용 패키지의 예시입니다.
감사 (Acknowledgements)
이 PEP는 Ivan Levkivskyi, Jelle Zijlstra, Alyssa Coghlan, Daniel F Moisset, Andrey Vlasovskikh, Nathaniel Smith, 그리고 Guido van Rossum의 아이디어, 피드백, 지원 없이는 불가능했을 것입니다.
버전 기록 (Version History)
- 2023-01-13: 모듈 해석 순서의 4단계가
py.typed
마커 파일이 있는 모든 패키지(인라인 패키지뿐만 아니라)에 적용됨을 명확히 했습니다. - 2021-09-20: 스텁 전용 네임스페이스 패키지에 대한 기대치와 타입 체커 동작을 명확히 했습니다. 네임스페이스 패키지 내의 단일 파일 모듈 처리 방식을 명확히 했습니다.
- 2018-07-09: 샘플 스텁 전용 패키지에 대한 링크를 추가했습니다.
- 2018-06-19: 부분적 스텁 패키지가 런타임 패키지뿐만 아니라 typeshed도 확인할 수 있도록 했습니다.
- 2018-05-15: 부분적 스텁 패키지 사양을 추가했습니다.
- 2018-04-09: mypy 구현에 대한 참조를 추가했습니다. 스텁 패키지 우선순위를 명확히 했습니다.
- 2018-02-02: 스텁 전용 패키지 접미사를
_stubs
에서-stubs
로 변경했습니다. 스텁 전용 패키지에는py.typed
가 필요 없음을 명시했습니다. pip 및 스텁 패키지 업그레이드에 대한 참고 사항을 추가했습니다. - 2017-11-12: 기존 툴링만 사용하도록 다시 작성했습니다. 메타데이터에 타입 정보 종류를 명시할 필요가 없습니다. 마커 파일 이름이
.typeinfo
에서py.typed
로 변경되었습니다. - 2017-11-10: 배포 메타데이터 대신 패키지 메타데이터를 사용하도록 사양을 다시 작성했습니다. 스텁 전용 패키지를 제거하고 서드파티 패키지 사양에 병합했습니다. 타입 체커가 런타임 버전을 검사하는 것에 대한 제안을 제거했습니다. PEP 변경 사항을 반영하여 구현을 업데이트했습니다.
- 2017-10-26: 구현 참조를 추가했습니다. 감사 및 버전 기록을 추가했습니다.
- 2017-10-06:
distutils
특정 명령 대신.distinfo/METADATA
를 사용하도록 다시 작성했습니다. 서드파티 스텁 패키지의 버전 관리를 명확히 했습니다. - 2017-09-11: 현재 해결책 및 typeshed에 대한 정보를 추가했습니다. 배경(rationale)을 명확히 했습니다.
참조 (References)
- Typeshed
- 타입 체커의 예시 구현 (mypy docs)
- [stub_package] 스텁 전용 패키지
- [typed_package] 샘플 타입 지정 패키지
- [numpy-stubs] numpy용 스텁
- [pkg_checker] 샘플 패키지 체커
저작권 (Copyright)
이 문서는 퍼블릭 도메인에 공개되었습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments