PEP 593 – 유연한 함수 및 변수 어노테이션
- Author:
- Till Varoquaux <till.varoquaux at gmail.com>, Konstantin Kashin <kkashin at fb.com>
- Sponsor:
- Ivan Levkivskyi <levkivskyi at gmail.com>
- Discussions-To:
- Typing-SIG list
- Status:
- Final
- Type:
- Standards Track
- Topic:
- Typing
- Created:
- 26-Apr-2019
- Python-Version:
- 3.9
- Post-History:
- 20-May-2019
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
개요
이 PEP는 PEP 484의 타입 어노테이션을 임의의 메타데이터로 확장하는 메커니즘을 도입합니다.
동기
PEP 484는 PEP 3107에서 도입된 어노테이션에 대한 표준 의미론을 제공합니다. PEP 484는 규범적이지만 어노테이션을 사용하는 대부분의 사용자에게 사실상의 표준입니다. 타입 어노테이션이 널리 사용되는 정적 검사 코드베이스에서는 다른 형태의 어노테이션이 사실상 밀려났습니다. 관련 PEP 3107에 설명된 어노테이션의 일부 사용 사례(데이터베이스 매핑, 외국어 브리지)는 타입 어노테이션이 널리 사용되는 현 상황을 고려하면 현재로서는 현실적이지 않습니다. 또한 타입 어노테이션의 표준화는 특정 타입 검사기만 지원하는 고급 기능을 배제합니다.
근거
이 PEP는 기존 타입을 컨텍스트별 메타데이터로 장식하기 위해 typing 모듈에 Annotated 타입을 추가합니다. 구체적으로 타입 T에는 Annotated[T, x] 타입 힌트를 통해 메타데이터 x를 추가할 수 있습니다. 이 메타데이터는 정적 분석이나 런타임에서 사용할 수 있습니다. 라이브러리(또는 도구)가 Annotated[T, x] 타입 힌트를 발견했지만 메타데이터 x를 위한 특별한 로직이 없다면, 이를 무시하고 단순히 타입을 T로 취급해야 합니다. 현재 typing 모듈에 존재하며 함수나 클래스의 어노테이션 타입 검사를 완전히 비활성화하는 no_type_check 기능과 달리, Annotated 타입은 특정 애플리케이션 내에서 x에 런타임으로 접근하는 동시에 T를 정적으로 타입 검사할 수 있도록 합니다(예를 들어 mypy 또는 Pyre를 통해 x를 안전하게 무시할 수 있습니다). 이 타입을 도입하면 더 광범위한 Python 커뮤니티가 관심을 갖는 다양한 사용 사례를 해결할 수 있습니다.
이는 원래 typing github에서 issue 600로 제기되었으며 이후 Python ideas에서 논의되었습니다.
동기를 부여하는 예시
어노테이션의 런타임 및 정적 사용 결합
라이브러리가 런타임에서 typing 어노테이션을 활용하는 추세가 나타나고 있습니다(예: dataclasses). 어노테이션을 외부 데이터로 확장할 수 있다면 이러한 라이브러리에 큰 도움이 될 것입니다.
다음은 가상의 모듈이 어노테이션을 활용하여 C 구조체를 읽는 방법의 예시입니다.:
UnsignedShort = Annotated[int, struct2.ctype('H')]
SignedChar = Annotated[int, struct2.ctype('b')]
class Student(struct2.Packed):
# mypy typechecks 'name' field as 'str'
name: Annotated[str, struct2.ctype("<10s")]
serialnum: UnsignedShort
school: SignedChar
# 'unpack' only uses the metadata within the type annotations
Student.unpack(record)
# Student(name=b'raymond ', serialnum=4658, school=264)
새로운 typing 구성 요소 개발의 장벽 낮추기
일반적으로 새 타입을 추가할 때 개발자는 해당 타입을 typing 모듈에 상류로 반영하고 mypy, PyCharm, Pyre, pytype 등을 변경해야 합니다. 이는 이러한 타입을 사용하는 오픈 소스 코드를 작업할 때 특히 중요합니다. 추가 로직 없이는 해당 코드가 다른 개발자의 도구로 즉시 이식되지 않기 때문입니다. 그 결과 코드베이스에서 새 타입을 개발하고 시험해 보는 데 드는 비용이 높습니다. 이상적으로는 작성자가 점진적 성능 저하를 허용하는 방식으로 새 타입을 도입할 수 있어야 합니다(예: 클라이언트에게 사용자 정의 mypy plugin이 없는 경우). 이는 개발 장벽을 낮추고 어느 정도의 하위 호환성을 보장합니다.
예를 들어 작성자가 Python에 tagged unions에 대한 지원을 추가하려 한다고 가정해 보십시오. 이를 달성하는 한 가지 방법은 Python에서 annotate TypedDict를 주석 처리하여 오직 하나의 필드만 설정할 수 있도록 하는 것입니다.:
Currency = Annotated[
TypedDict('Currency', {'dollars': float, 'pounds': float}, total=False),
TaggedUnion,
]
다소 번거로운 구문이지만, 이를 통해 이 개념 증명을 반복 개선할 수 있으며 아직 이 기능을 지원하지 않는 타입 검사기(또는 기타 도구)를 사용하는 사람도 태그된 유니언을 포함한 코드베이스에서 작업할 수 있습니다. 작성자는 태그된 유니언을 typing, mypy 등에 상류로 반영하기 전에 이 제안을 쉽게 테스트하고 문제점을 해결할 수 있습니다. 또한 TaggedUnion 어노테이션을 구문 분석하는 기능이 없는 도구도 여전히 Currency를 TypedDict로 취급할 수 있으며, 이는 여전히 근접한 근사치입니다(조금 덜 엄격합니다).
사양
구문
Annotated은 타입과 어노테이션을 나타내는 임의의 Python 값 목록을 매개변수로 받습니다. 구문의 구체적인 세부 사항은 다음과 같습니다.
Annotated의 첫 번째 인자는 유효한 타입이어야 합니다.- 여러 타입 어노테이션이 지원됩니다 (
Annotated은 가변 인자를 지원합니다).:Annotated[int, ValueRange(3, 10), ctype("char")]
Annotated은 최소 두 개의 인자로 호출해야 합니다 (Annotated[int]은 유효하지 않습니다).- 어노테이션의 순서는 보존되며 동등성 검사에 영향을 줍니다.:
Annotated[int, ValueRange(3, 10), ctype("char")] != Annotated[ int, ctype("char"), ValueRange(3, 10) ]
- 중첩된
Annotated타입은 평탄화되며, 메타데이터의 순서는 가장 안쪽 어노테이션부터 시작합니다.:Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[ int, ValueRange(3, 10), ctype("char") ]
- 중복된 어노테이션은 제거되지 않습니다.:
Annotated[int, ValueRange(3, 10)] != Annotated[ int, ValueRange(3, 10), ValueRange(3, 10) ]
Annotated은 중첩된 별칭 및 제네릭 별칭과 함께 사용할 수 있습니다.:Typevar T = ... Vec = Annotated[List[Tuple[T, T]], MaxLen(10)] V = Vec[int] V == Annotated[List[Tuple[int, int]], MaxLen(10)]
어노테이션 소비
궁극적으로 어노테이션을 해석할 방법이 있다면 그 방법을 결정할 책임은 Annotated타입을 접하는 도구 또는 라이브러리에 있습니다. Annotated타입을 접하는 도구 또는 라이브러리는 어노테이션을 훑어보며 관심 대상인지 확인할 수 있습니다 (예: isinstance() 사용).
알 수 없는 어노테이션: 도구 또는 라이브러리가 어노테이션을 지원하지 않거나 알 수 없는 어노테이션을 접하면 이를 무시하고 어노테이션이 적용된 타입을 기반 타입으로 취급해야 합니다. 예를 들어, 이름에 대한 어노테이션 중 struct2.ctype의 인스턴스가 아닌 어노테이션(예: Annotated[str, 'foo', struct2.ctype("<10s")])을 접하면 unpack 메서드는 이를 무시해야 합니다.
어노테이션 네임스페이싱: 어노테이션에 사용되는 클래스가 네임스페이스 역할을 하므로 어노테이션에 네임스페이스는 필요하지 않습니다.
여러 어노테이션: 클라이언트가 하나의 타입에 여러 어노테이션을 지정하도록 허용할지와 해당 어노테이션을 어떻게 병합할지는 어노테이션을 소비하는 도구가 결정합니다.
Annotated 타입을 사용하면 모든 노드에 동일한 타입 또는 서로 다른 타입의 여러 어노테이션을 배치할 수 있으므로, 해당 어노테이션을 소비하는 도구 또는 라이브러리가 잠재적인 중복을 처리해야 합니다. 예를 들어 값 범위 분석을 수행하는 경우 다음을 허용할 수 있습니다.:
T1 = Annotated[int, ValueRange(-10, 5)]
T2 = Annotated[T1, ValueRange(-20, 3)]
중첩된 어노테이션을 평탄화하면 다음과 같이 변환됩니다.:
T2 = Annotated[int, ValueRange(-10, 5), ValueRange(-20, 3)]
get_type_hints()와의 상호 작용
typing.get_type_hints()는 하위 호환성을 유지하기 위해 기본값이 False인 새 인자 include_extras를 받습니다. include_extras가 False이면 추가 어노테이션은 반환값에서 제거됩니다. 그렇지 않으면 어노테이션이 변경되지 않은 채 반환됩니다.:
@struct2.packed
class Student(NamedTuple):
name: Annotated[str, struct.ctype("<10s")]
get_type_hints(Student) == {'name': str}
get_type_hints(Student, include_extras=False) == {'name': str}
get_type_hints(Student, include_extras=True) == {
'name': Annotated[str, struct.ctype("<10s")]
}
별칭 및 장황함에 대한 우려
typing.Annotated를 어디에나 작성하는 것은 상당히 장황할 수 있지만, 다행히 어노테이션에 별칭을 지정할 수 있으므로 실제로는 클라이언트가 상용구 코드를 많이 작성할 필요가 없습니다.:
T = TypeVar('T')
Const = Annotated[T, my_annotations.CONST]
class C:
def const_method(self: Const[List[int]]) -> int:
...
기각된 아이디어
제안된 아이디어 중 일부는 Annotated를 다른 타이핑 어노테이션과 깔끔하게 통합할 수 없게 만들기 때문에 이 PEP에서 기각되었습니다.
Annotated는 데코레이트된 타입을 추론할 수 없습니다.Annotated[..., Immutable]를 사용하여 값의 타입은 계속 추론하면서 해당 값을 변경 불가능한 것으로 표시할 수 있다고 생각할 수 있습니다. 타이핑은 추론된 타입을 anywhere else에서 사용하는 것을 지원하지 않으므로, 이를 특수한 경우로 추가하지 않는 것이 가장 좋습니다.(Type, Ann1, Ann2, ...)를Annotated[Type, Ann1, Ann2, ...]대신 사용하는 것입니다. 이렇게 하면 중첩된 위치에 어노테이션이 나타날 때 혼란이 발생합니다 (Callable[[A, B], C]는Callable[[(A, B)], C]와 너무 유사합니다). 또한 생성자가 그대로 전달되는 것이 불가능해집니다 (C = Annotation[T, Ann]일 때T(5) == C(5)).
이 기능은 설계를 단순하게 유지하기 위해 제외되었습니다.
Annotated는 단일 인자로 호출할 수 없습니다. Annotated는 단일 인자로 호출되었을 때 기저 값을 반환하는 것을 지원할 수도 있었습니다(예:Annotated[int] == int). 이는 명세를 복잡하게 만들 뿐 이점은 거의 없습니다.
Copyright
This document has been placed in the public domain.