PEP 589 – TypedDict: 고정된 키 집합을 갖는 딕셔너리를 위한 타입 힌트
- Author:
- Jukka Lehtosalo <jukka.lehtosalo at iki.fi>
- Sponsor:
- Guido van Rossum <guido at python.org>
- BDFL-Delegate:
- Guido van Rossum <guido at python.org>
- Discussions-To:
- Typing-SIG list
- Status:
- Final
- Type:
- Standards Track
- Topic:
- Typing
- Created:
- 20-Mar-2019
- Python-Version:
- 3.8
- Post-History:
- Resolution:
- Typing-SIG message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
PEP 484는 모든 값이 같은 타입을 가지며 임의의 키 값을 지원하는 균일한 딕셔너리를 위해 Dict[K, V] 타입을 정의합니다. 하지만 이 타입은 딕셔너리 값의 타입이 키의 문자열 값에 따라 달라지는 흔한 패턴을 제대로 지원하지 않습니다. 이 PEP는 딕셔너리 객체가 특정한 문자열 키 집합을 가지고 각 키에 특정 타입의 값이 대응하는 사용 사례를 지원하기 위해 타입 생성자 typing.TypedDict를 제안합니다.
다음은 PEP 484로는 만족스럽게 어노테이션할 수 없는 예입니다:
movie = {'name': 'Blade Runner',
'year': 1982}
이 PEP는 movie의 타입을 정확하게 표현할 수 있도록 TypedDict라는 새로운 타입 생성자를 추가할 것을 제안합니다:
from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
이제 타입 검사기는 이 코드를 받아들여야 합니다:
movie: Movie = {'name': 'Blade Runner',
'year': 1982}
동기
(사용자 정의 클래스 대신) 문자열 키를 갖는 (중첩될 수도 있는) 딕셔너리를 사용하여 객체나 구조화된 데이터를 표현하는 것은 Python 프로그램에서 흔한 패턴입니다. JSON 객체를 표현하는 것이 아마도 대표적인 사용 사례일 것이며, 이는 Python에 JSON 라이브러리가 기본으로 포함될 만큼 널리 쓰입니다. 이 PEP는 이러한 코드를 더 효과적으로 타입 검사할 수 있는 방법을 제안합니다.
더 일반적으로 말하면, 딕셔너리, 문자열, 리스트와 같은 Python 기본 타입만을 사용하여 순수 데이터 객체를 표현하는 방식에는 나름의 매력이 있었습니다. 이러한 객체는 JSON을 사용하지 않을 때에도 직렬화와 역직렬화가 쉽습니다. 또한 (str() 및 pprint 모듈을 통한) 보기 좋은 출력, 반복, 동등성 비교를 비롯한 다양하고 유용한 연산을 추가 작업 없이 손쉽게 지원합니다.
PEP 484는 위에서 언급한 사용 사례를 제대로 지원하지 않습니다. 값 타입이 str인 'name'과 값 타입이 int인 'year'라는, 정확히 두 개의 유효한 문자열 키를 갖는 딕셔너리 객체를 생각해 봅시다. PEP 484의 Dict[str, Any] 타입이 적합하겠지만, 임의의 문자열 키를 사용할 수 있고 임의의 값이 유효하므로 너무 느슨합니다. 마찬가지로 Dict[str, Union[str, int]]도 'name' 키의 값이 int일 수 있고 임의의 문자열 키가 허용되므로 너무 일반적입니다. 또한 (d가 이 타입의 딕셔너리라고 가정할 때) d['name']과 같은 첨자 표현식의 타입은 Union[str, int]가 되는데, 이는 너무 넓습니다.
데이터 클래스는 이 사용 사례를 해결하는 더 최근의 대안이지만, 데이터 클래스가 도입되기 전에 작성된 기존 코드가 여전히 많으며, 특히 타입 힌트와 타입 검사가 유용하다고 입증된 대규모 기존 코드베이스에서 그렇습니다. 딕셔너리 객체와 달리 데이터 클래스는 JSON 직렬화를 직접 지원하지 않지만, 이를 구현한 서드파티 패키지가 있습니다 [1].
명세
TypedDict 타입은 특정한 문자열 키 집합을 가지며 각 유효한 키에 대해 특정한 값 타입을 갖는 딕셔너리 객체를 나타냅니다. 각 문자열 키는 필수(반드시 존재해야 함)이거나 비필수(존재하지 않아도 됨)일 수 있습니다.
이 PEP는 TypedDict 타입을 정의하는 두 가지 방법을 제안합니다. 첫 번째는 클래스 기반 문법을 사용합니다. 두 번째는 하위 호환성을 위해 제공되는 대안인 할당 기반 문법으로, 이 기능을 이전 Python 버전으로 백포트할 수 있게 합니다. 그 근거는 PEP 484가 Python 2.7을 위해 주석 기반 어노테이션 문법을 지원하는 이유와 비슷합니다. 타입 힌트는 대규모 기존 코드베이스에서 특히 유용하며, 이러한 코드베이스는 이전 Python 버전에서 실행되어야 하는 경우가 많기 때문입니다. 두 가지 문법 옵션은 typing.NamedTuple이 지원하는 문법 변형과 대응합니다. 그 밖에 제안된 기능으로는 TypedDict 상속과 전체성(키가 필수인지 여부를 지정하는 것)이 있습니다.
이 PEP는 또한 타입 검사기가 TypedDict 객체와 관련된 타입 검사 연산을 어떻게 지원해야 하는지에 대한 개요를 제공합니다. PEP 484와 마찬가지로, 매우 다양한 타입 검사 접근 방식을 실험할 수 있도록 이 논의는 의도적으로 다소 모호하게 남겨 두었습니다. 특히 타입 호환성은 구조적 호환성에 기반해야 합니다. 즉, 더 구체적인 TypedDict 타입은 더 작은(더 일반적인) TypedDict 타입과 호환될 수 있습니다.
클래스 기반 문법
TypedDict 타입은 typing.TypedDict를 유일한 베이스 클래스로 하는 클래스 정의 문법을 사용하여 정의할 수 있습니다.:
from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
Movie는 'name'(타입 str)과 'year'(타입 int)라는 두 항목을 가진 TypedDict 타입입니다.
타입 검사기는 클래스 기반 TypedDict 정의의 본문이 다음 규칙을 따르는지 검증해야 합니다.
- 클래스 본문에는
key: value_type형식의 항목 정의가 있는 줄만 포함되어야 하며, 그 앞에 선택적으로 독스트링이 올 수 있습니다. 항목 정의 문법은 속성 어노테이션과 동일하지만, 초기화 식이 없어야 하며, 키 이름은 실제로 속성 이름이 아니라 키의 문자열 값을 가리킵니다. - 클래스 기반
NamedTuple문법과의 일관성을 위해 클래스 기반 문법에서는 타입 주석을 사용할 수 없습니다. (Python 2.7과의 하위 호환성을 위해 타입 주석을 지원하는 것만으로는 충분하지 않다는 점에 유의하십시오. 아래에서 설명하듯이 클래스 정의에total키워드 인자가 있을 수 있는데, 이는 Python 2.7에서 유효한 문법이 아니기 때문입니다.) 대신 이 PEP는 하위 호환성을 위해 대입 기반의 대체 문법을 제공하며, 이는 Alternative Syntax에서 설명합니다. - 값 타입에는 문자열 리터럴 전방 참조를 사용할 수 있습니다.
- TypedDict 객체의 런타임 타입은 항상 그저
dict이므로(결코dict의 서브클래스가 아닙니다) 메서드는 허용되지 않습니다. - 메타클래스를 지정하는 것은 허용되지 않습니다.
본문에 pass만 포함하여 빈 TypedDict를 만들 수 있습니다(독스트링이 있으면 pass를 생략할 수 있습니다):
class EmptyDict(TypedDict):
pass
TypedDict 타입 사용하기
다음은 Movie 타입을 사용하는 방법의 예입니다:
movie: Movie = {'name': 'Blade Runner',
'year': 1982}
일반적으로 명시적인 Movie 타입 어노테이션이 필요합니다. 그렇지 않으면 하위 호환성을 위해 타입 검사기가 일반 딕셔너리 타입으로 간주할 수 있기 때문입니다. 생성된 딕셔너리 객체가 TypedDict여야 한다는 것을 타입 검사기가 추론할 수 있는 경우에는 명시적인 어노테이션을 생략할 수 있습니다. 대표적인 예는 함수 인자로 쓰이는 딕셔너리 객체입니다. 이 예에서 타입 검사기는 딕셔너리 인자를 TypedDict로 이해해야 한다고 추론할 것으로 기대됩니다:
def record_movie(movie: Movie) -> None: ...
record_movie({'name': 'Blade Runner', 'year': 1982})
타입 검사기가 딕셔너리 디스플레이를 TypedDict로 취급해야 하는 또 다른 예는 이전에 TypedDict 타입으로 선언된 변수에 할당하는 경우입니다:
movie: Movie
...
movie = {'name': 'Blade Runner', 'year': 1982}
movie에 대한 연산은 정적 타입 검사기로 검사할 수 있습니다:
movie['director'] = 'Ridley Scott' # Error: invalid key 'director'
movie['year'] = '1982' # Error: invalid value type ("int" expected)
'title'은 유효한 키가 아니고 'name' 키가 누락되었으므로 아래 코드는 거부되어야 합니다:
movie2: Movie = {'title': 'Blade Runner',
'year': 1982}
생성된 TypedDict 타입 객체는 실제 클래스 객체가 아닙니다. 타입 검사기가 허용해야 하는 이 타입의 용도는 다음이 전부입니다.
- 타입 어노테이션에서, 그리고 타입 별칭이나 cast의 대상 타입처럼 임의의 타입 힌트가 유효한 모든 문맥에서 사용할 수 있습니다.
- TypedDict 항목에 대응하는 키워드 인자를 받는 호출 가능 객체로 사용할 수 있습니다. 키워드가 아닌 인자는 허용되지 않습니다. 예시:
m = Movie(name='Blade Runner', year=1982)
호출되면 TypedDict 타입 객체는 런타임에 일반 딕셔너리 객체를 반환합니다:
print(type(m)) # <class 'dict'>
- 베이스 클래스로 사용할 수 있지만, 파생 TypedDict를 정의할 때만 가능합니다. 이에 대해서는 아래에서 더 자세히 설명합니다.
특히 TypedDict 타입 객체는 isinstance(d, Movie)와 같은 isinstance() 검사에 사용할 수 없습니다. 딕셔너리 항목 값의 타입을 검사하는 기능이 현재 지원되지 않기 때문입니다. isinstance()는 List[str]과 같은 흔한 타입을 포함하여 많은 PEP 484 타입에서 동작하지 않습니다. 이는 다음과 같은 경우에 필요합니다:
class Strings(TypedDict):
items: List[str]
print(isinstance({'items': [1]}, Strings)) # Should be False
print(isinstance({'items': ['x']}, Strings)) # Should be True
위의 사용 사례는 지원되지 않습니다. 이는 List[str]에 대해 isinstance()가 지원되지 않는 것과 일관됩니다.
상속
클래스 기반 문법을 사용하면 TypedDict 타입이 하나 이상의 TypedDict 타입을 상속할 수 있습니다. 이 경우 TypedDict 베이스 클래스는 포함하지 않아야 합니다. 예시:
class BookBasedMovie(Movie):
based_on: str
이제 BookBasedMovie에는 name, year, based_on 키가 있습니다. TypedDict 타입은 구조적 호환성을 사용하므로, 이는 다음 정의와 동등합니다:
class BookBasedMovie(TypedDict):
name: str
year: int
based_on: str
다음은 다중 상속의 예입니다:
class X(TypedDict):
x: int
class Y(TypedDict):
y: str
class XYZ(X, Y):
z: bool
TypedDict XYZ에는 x (타입 int), y (타입 str), z (타입 bool)의 세 항목이 있습니다.
TypedDict는 TypedDict 타입과 TypedDict가 아닌 베이스 클래스를 동시에 상속할 수 없습니다.
TypedDict 클래스 상속에 관한 추가 참고 사항:
- 서브클래스에서 부모 TypedDict 클래스의 필드 타입을 변경하는 것은 허용되지 않습니다. 예시:
class X(TypedDict): x: str class Y(X): x: int # Type check error: cannot overwrite TypedDict field "x"
위에서 설명한 예에서 TypedDict 클래스 어노테이션은 키
x에 대해 타입str을 반환합니다:print(Y.__annotations__) # {'x': <class 'str'>}
- 다중 상속에서는 이름이 같은 필드에 서로 충돌하는 타입을 허용하지 않습니다:
class X(TypedDict): x: int class Y(TypedDict): x: str class XYZ(X, Y): # Type check error: cannot overwrite TypedDict field "x" while merging xyz: bool
전체성
기본적으로 TypedDict에는 모든 키가 있어야 합니다. 전체성(totality)을 지정하여 이를 재정의할 수 있습니다. 클래스 기반 문법으로는 다음과 같이 합니다.:
class Movie(TypedDict, total=False):
name: str
year: int
이는 Movie TypedDict에서 어떤 키든 생략할 수 있다는 의미입니다. 따라서 다음은 유효합니다:
m: Movie = {}
m2: Movie = {'year': 2015}
타입 검사기는 total 인자의 값으로 리터럴 False 또는 True만 지원하면 됩니다. True가 기본값이며, 클래스 본문에 정의된 모든 항목을 필수로 만듭니다.
전체성 플래그는 TypedDict 정의의 본문에 정의된 항목에만 적용됩니다. 상속된 항목은 영향을 받지 않으며, 대신 그 항목이 정의된 TypedDict 타입의 전체성을 따릅니다. 이를 통해 하나의 TypedDict 타입 안에 필수 키와 필수가 아닌 키를 함께 둘 수 있습니다.
대체 문법
이 PEP는 또한 PEP 526에서 도입된 변수 정의 문법을 지원하지 않는 3.5와 2.7 같은 이전 Python 버전에 백포트할 수 있는 대체 문법을 제안합니다. 이는 네임드 튜플을 정의하는 전통적인 문법과 비슷합니다.:
Movie = TypedDict('Movie', {'name': str, 'year': int})
대체 문법으로도 전체성을 지정할 수 있습니다.:
Movie = TypedDict('Movie',
{'name': str, 'year': int},
total=False)
의미는 클래스 기반 문법과 동일합니다. 그러나 이 문법은 상속을 지원하지 않으며, 하나의 타입 안에 필수 필드와 필수가 아닌 필드를 함께 둘 방법이 없습니다. 그 이유는 가장 일반적인 사용 사례를 다루면서도 하위 호환 문법을 가능한 한 단순하게 유지하기 위해서입니다.
타입 검사기는 TypedDict의 두 번째 인자로 딕셔너리 디스플레이 표현식만 받아들이면 됩니다. 특히 구현을 단순하게 하기 위해, 딕셔너리 객체를 가리키는 변수는 지원하지 않아도 됩니다.
타입 일관성
비공식적으로 말하면, 타입 일관성은 Any 타입을 지원하도록 서브타입 관계(is-subtype-of)를 일반화한 것입니다. 더 형식적인 정의는 PEP 483에 있습니다. 이 절에서는 TypedDict 타입의 타입 일관성을 지원하는 데 필요한, 새롭고 자명하지 않은 규칙을 소개합니다.
첫째, 모든 TypedDict 타입은 Mapping[str, object]와 일관됩니다. 둘째, A가 B와 구조적으로 호환되면 TypedDict 타입 A는 TypedDict B와 일관됩니다. 이는 다음 두 조건을 모두 만족할 때, 그리고 그때에만 성립합니다.
B의 각 키에 대해A에 대응하는 키가 있고,A에서 그 키에 대응하는 값 타입이B의 값 타입과 일관됩니다.B의 각 키에 대해B의 값 타입도A에서 대응하는 값 타입과 일관됩니다.B의 각 필수 키에 대해, 대응하는 키가A에서도 필수입니다.B의 각 비필수 키에 대해, 대응하는 키가A에서도 필수가 아닙니다.
논의:
- TypedDict 객체는 변경 가능하므로 값 타입은 불변적(invariant)으로 동작합니다. 이는
List나Dict같은 변경 가능한 컨테이너 타입과 비슷합니다. 이것이 관련되는 예시:class A(TypedDict): x: Optional[int] class B(TypedDict): x: int def f(a: A) -> None: a['x'] = None b: B = {'x': 0} f(b) # Type check error: 'B' not compatible with 'A' b['x'] + 1 # Runtime error: None + 1
- 필수 키가 있는 TypedDict 타입은 같은 키가 비필수 키인 TypedDict 타입과 일관되지 않습니다. 후자는 키 삭제를 허용하기 때문입니다. 이것이 관련되는 예시:
class A(TypedDict, total=False): x: int class B(TypedDict): x: int def f(a: A) -> None: del a['x'] b: B = {'x': 0} f(b) # Type check error: 'B' not compatible with 'A' b['x'] + 1 # Runtime KeyError: 'x'
- 키
'x'가 없는 TypedDict 타입A는 비필수 키'x'가 있는 TypedDict 타입과 일관되지 않습니다. 런타임에 키'x'가 존재하면서 호환되지 않는 타입을 가질 수 있기 때문입니다(구조적 서브타이핑 때문에 이는A를 통해서는 보이지 않을 수 있습니다). 예시:class A(TypedDict, total=False): x: int y: int class B(TypedDict, total=False): x: int class C(TypedDict, total=False): x: int y: str def f(a: A) -> None: a['y'] = 1 def g(b: B) -> None: f(b) # Type check error: 'B' incompatible with 'A' c: C = {'x': 0, 'y': 'foo'} g(c) c['y'] + 'bar' # Runtime error: int + str
- TypedDict는 어떤
Dict[...]타입과도 일관되지 않습니다. 딕셔너리 타입은clear()를 비롯한 파괴적 연산을 허용하기 때문입니다. 또한 딕셔너리 타입은 임의의 키를 설정하는 것도 허용하는데, 이는 타입 안전성을 해칩니다. 예시:class A(TypedDict): x: int class B(A): y: str def f(d: Dict[str, int]) -> None: d['y'] = 0 def g(a: A) -> None: f(a) # Type check error: 'A' incompatible with Dict[str, int] b: B = {'x': 0, 'y': 'foo'} g(b) b['y'] + 'bar' # Runtime error: int + str
- 값이 모두
int인 TypedDict는Mapping[str, int]와 일관되지 않습니다. 구조적 서브타이핑 때문에 타입을 통해서는 보이지 않는,int가 아닌 값이 추가로 있을 수 있기 때문입니다. 예를 들어 이러한 값은Mapping의values()와items()메서드를 사용해 접근할 수 있습니다. 예시:class A(TypedDict): x: int class B(TypedDict): x: int y: str def sum_values(m: Mapping[str, int]) -> int: n = 0 for v in m.values(): n += v # Runtime error return n def f(a: A) -> None: sum_values(a) # Error: 'A' incompatible with Mapping[str, int] b: B = {'x': 0, 'y': 'foo'} f(b)
지원되는 연산과 지원되지 않는 연산
타입 검사기는 TypedDict 객체에 대해 대부분의 dict 연산을 제한된 형태로 지원해야 합니다. 기본 원칙은 Any 타입이 관여하지 않는 연산이 런타임 타입 안전성을 위반할 수 있다면 타입 검사기가 이를 거부해야 한다는 것입니다. 다음은 방지해야 할 가장 중요한 타입 안전성 위반 사례 중 일부입니다:
- 필수 키가 누락되었습니다.
- 값의 타입이 유효하지 않습니다.
- TypedDict 타입에 정의되지 않은 키가 추가되었습니다.
리터럴이 아닌 키는 타입 검사 중에 그 값을 알 수 없어서 위의 위반 사례 중 일부를 일으킬 수 있으므로 일반적으로 거부해야 합니다. (Use of Final Values and Literal Types에서는 이를 final 이름과 리터럴 타입까지 포괄하도록 일반화합니다.)
존재한다고 알려지지 않은 키를 사용하는 경우, 런타임 타입 오류가 반드시 발생하지는 않더라도 오류로 보고해야 합니다. 이러한 사용은 흔히 실수이며, 구조적 서브타이핑 때문에 특정 항목의 타입이 가려지면 유효하지 않은 타입의 값이 삽입될 수 있습니다. 예를 들어 'x'가 d(TypedDict 타입이라고 가정합니다)의 유효한 키가 아니라면 d['x'] = 1은 타입 검사 오류를 발생시켜야 합니다.
TypedDict 객체를 생성할 때 포함된 추가 키도 잡아내야 합니다. 이 예제에서 director 키는 Movie에 정의되어 있지 않으므로 타입 검사기가 오류를 발생시킬 것으로 예상됩니다:
m: Movie = dict(
name='Alien',
year=1979,
director='Ridley Scott') # error: Unexpected key 'director'
타입 검사기는 다음 연산이 일반 딕셔너리에서는 유효하더라도 TypedDict 객체에서는 안전하지 않은 것으로 간주하여 거부해야 합니다:
- 문자열 리터럴이나 문자열 값이 알려진 다른 표현식 대신 임의의
str키를 사용하는 연산은 일반적으로 거부해야 합니다. 여기에는 항목 설정과 같은 파괴적 연산과 첨자 표현식과 같은 읽기 전용 연산이 모두 포함됩니다. 위 규칙의 예외로, 타입이str인 임의의 표현식e에 대해 TypedDict 객체에서d.get(e)와e in d는 허용해야 합니다. 이러한 연산은 안전하며 TypedDict 객체를 내부 검사하는 데 유용할 수 있기 때문입니다.e의 문자열 값을 정적으로 결정할 수 없다면d.get(e)의 정적 타입은object여야 합니다. clear()는 필수 키를 제거할 수 있으므로 안전하지 않으며, 그러한 키 중 일부는 구조적 서브타이핑 때문에 직접 보이지 않을 수도 있습니다.popitem()도 알려진 모든 키가 필수가 아니더라도(total=False) 마찬가지로 안전하지 않습니다.del obj['key']는'key'가 필수가 아닌 키가 아니라면 거부해야 합니다.
타입 검사기는 'x' 키가 필수가 아니더라도 d.get('x')나 명시적인 'x' in d 검사를 요구하는 대신 d['x']로 항목을 읽는 것을 허용할 수 있습니다. 그 근거는 키의 존재 여부를 추적하는 기능을 완전히 일반적인 형태로 구현하기 어렵고, 이를 금지하면 기존 코드를 많이 변경해야 할 수 있다는 것입니다.
정확한 타입 검사 규칙은 각 타입 검사기가 결정합니다. 경우에 따라서는 잠재적으로 안전하지 않은 연산을 거부할 때 관용적인 코드에서 거짓 양성 오류가 발생한다면, 그 연산을 허용할 수도 있습니다.
final 값과 리터럴 타입의 사용
타입 검사기는 TypedDict 객체에 대한 연산에서 문자열 리터럴 대신 문자열 값을 가진 final 이름(PEP 591)을 사용할 수 있도록 허용해야 합니다. 예를 들어 다음은 유효합니다:
YEAR: Final = 'year'
m: Movie = {'name': 'Alien', 'year': 1979}
years_since_epoch = m[YEAR] - 1970
마찬가지로 리터럴 값 대신 적절한 리터럴 타입(PEP 586)을 가진 표현식을 사용할 수 있습니다:
def get_value(movie: Movie,
key: Literal['year', 'name']) -> Union[int, str]:
return movie[key]
TypedDict 타입 정의에서 키를 지정할 때 타입 검사기는 final 이름이나 리터럴 타입이 아닌 실제 문자열 리터럴만 지원하면 됩니다. 또한 TypedDict 정의에서 전체성(totality)을 지정할 때는 불리언 리터럴만 사용할 수 있습니다. 이렇게 하는 이유는 타입 선언이 그 자체로 완결되도록 하고 타입 검사기의 구현을 단순화하기 위해서입니다.
하위 호환성
하위 호환성을 유지하기 위해, 타입 검사기는 프로그래머가 TypedDict 타입을 의도했다는 것이 충분히 명확하지 않은 한 TypedDict 타입을 추론해서는 안 됩니다. 확실하지 않을 때는 일반 딕셔너리 타입을 추론해야 합니다. 그렇지 않으면 TypedDict 타입이 딕셔너리 타입보다 더 제한적이기 때문에, 타입 검사기에 TypedDict 지원이 추가되는 순간 오류 없이 타입 검사를 통과하던 기존 코드에서 오류가 발생하기 시작할 수 있습니다. 특히 TypedDict 타입은 딕셔너리 타입의 서브타입이 아닙니다.
참조 구현
mypy [2] 타입 검사기는 TypedDict 타입을 지원합니다. 런타임 구성 요소의 참조 구현은 typing_extensions [3] 모듈에서 제공됩니다. 최초 구현은 mypy_extensions [4] 모듈에 있었습니다.
기각된 대안
제안된 여러 아이디어가 기각되었습니다. 현재의 기능 집합은 상당히 많은 영역을 다루는 것으로 보이며, 제안된 확장 중 어떤 것이 미미한 수준 이상으로 유용할지 명확하지 않았습니다. 이 PEP는 나중에 확장될 수 있는 기본 기능을 정의합니다.
다음 사항들은 이 제안의 취지와 맞지 않으므로 원칙적으로 기각되었습니다:
- TypedDict는 확장 가능하지 않으며, 특정 사용 사례만을 다룹니다. TypedDict 객체는 런타임에 일반 딕셔너리이며, TypedDict는
dict의 서브클래스를 포함하여 딕셔너리와 유사하거나 매핑과 유사한 다른 클래스와 함께 사용할 수 없습니다. TypedDict 타입에 메서드를 추가할 방법은 없습니다. 이렇게 한 동기는 단순성입니다. - TypedDict 타입 정의를 딕셔너리의 런타임 타입 검사에 사용하는 것도 충분히 생각해 볼 수 있습니다. 예를 들어, JSON 객체가 TypedDict 타입이 지정하는 스키마를 따르는지 검증하는 데 사용할 수 있습니다. 이 제안은 정적 타입 검사에만 초점을 맞추고 있으며, Class-based Syntax에서 논의했듯이 기존의 다른 타입들도 이를 지원하지 않으므로, 이 PEP는 그러한 기능을 포함하지 않습니다. 예를 들어, 그러한 기능은
typing_inspect[5] 서드파티 모듈을 사용하는 서드파티 라이브러리가 제공할 수 있습니다. - TypedDict 타입은
isinstance()나issubclass()검사에 사용할 수 없습니다. 그 이유는 많은 타입 힌트에서 런타임 타입 검사를 일반적으로 지원하지 않는 이유와 비슷합니다.
다음 기능들은 이 PEP에서 제외되었지만, 향후 추가될 수 있는 확장입니다:
- TypedDict는 명시적으로 정의되지 않은 키에 대해 기본값 타입을 지정하는 기능을 지원하지 않습니다. 이 기능이 있으면 TypedDict 객체에 임의의 키를 사용할 수 있게 되며, 명시적으로 나열된 키만 일반적이고 균일한 딕셔너리 타입과 비교하여 특별한 취급을 받게 됩니다.
- 각 키가 필수인지 아닌지를 개별적으로 지정할 방법은 없습니다. 제안된 문법 중 충분히 명확한 것이 없었으며, 이 기능이 필요한 경우는 제한적일 것으로 예상합니다.
- TypedDict는
**kwargs인자의 타입을 지정하는 데 사용할 수 없습니다. 이 기능이 있으면 허용되는 키워드 인자와 그 타입을 제한할 수 있게 됩니다. PEP 484에 따르면, TypedDict 타입을**kwargs의 타입으로 사용하는 것은 해당 TypedDict가 임의의 키워드 인자의 값으로 유효하다는 뜻일 뿐, 어떤 키워드 인자가 허용되어야 하는지를 제한하지는 않습니다. 이를 위해**kwargs: Expand[T]문법이 제안된 바 있습니다 [6].
감사의 글
David Foster가 mypy에 TypedDict 타입의 초기 구현을 기여했습니다. 구현 개선에는 적어도 저자(Jukka Lehtosalo), Ivan Levkivskyi, Gareth T, Michael Lee, Dominik Miedzinski, Roy Williams, Max Moroz가 기여했습니다.
참고 문헌
Copyright
This document has been placed in the public domain.