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

Python 개선 제안 한국어 번역

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

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP is a historical document: see Typed dictionaries and typing.TypedDict for up-to-date specs and documentation. Canonical typing specs are maintained at the typing specs site; runtime typing behaviour is described in the CPython documentation.

×

See the typing specification update process for how to propose changes to the typing spec.

초록

PEP 484에서는 모든 값이 동일한 유형이고 임의의 키 값을 지원하는 균일한 딕셔너리를 위해 Dict[K, V]유형을 정의합니다. 하지만 딕셔너리 값의 유형이 키의 문자열 값에 따라 달라지는 일반적인 패턴을 제대로 지원하지는 않습니다. 이 PEP에서는 딕셔너리 객체가 특정 문자열 키 집합을 가지고 각 키에 특정 유형의 값이 대응하는 사용 사례를 지원하기 위해 typing.TypedDict유형 생성자를 제안합니다.

다음은 PEP 484로는 만족스럽게 어노테이션할 수 없는 예시입니다.:

movie = {'name': 'Blade Runner',
         'year': 1982}

이 PEP에서는 TypedDict라는 새 유형 생성자를 추가하여 movie의 유형을 정확하게 표현할 수 있도록 제안합니다.:

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는 위에서 언급한 사용 사례를 제대로 지원하지 않습니다. 유효한 문자열 키가 정확히 두 개이고, 'name'의 값 유형은 str, 'year'의 값 유형은 int인 딕셔너리 객체를 생각해 보겠습니다. Dict[str, Any]PEP 484 타입으로 적합하지만, 임의의 문자열 키를 사용할 수 있고 임의의 값이 유효하므로 지나치게 느슨합니다. 마찬가지로 Dict[str, Union[str, int]]도 너무 일반적입니다. 키 'name'의 값이 int일 수 있고 임의의 문자열 키가 허용되기 때문입니다. 또한 d['name']과 같은 구독 표현식의 유형은 (d가 이 유형의 딕셔너리라고 가정할 때) 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는 두 항목을 가진 TypedDict 타입입니다. 'name'(타입은 str)과 'year'(타입은 int)입니다.

타입 검사기는 클래스 기반 TypedDict 정의의 본문이 다음 규칙을 준수하는지 검증해야 합니다.

  • 클래스 본문에는 선택적으로 독스트링이 앞에 올 수 있는 key: value_type 형식의 항목 정의 줄만 포함해야 합니다. 항목 정의의 구문은 속성 어노테이션과 동일하지만, 초기화 구문이 없어야 하며 키 이름은 실제로 속성 이름이 아니라 키의 문자열 값을 가리킵니다.
  • 클래스 기반 NamedTuple구문과의 일관성을 위해 클래스 기반 구문에서는 타입 주석을 사용할 수 없습니다. (아래에서 설명하는 것처럼 클래스 정의에는 total 키워드 인자가 포함될 수 있으며, 이는 Python 2.7에서 유효한 구문이 아니므로 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 타입 객체는 실제 클래스 객체가 아닙니다. 다음은 타입 검사기가 허용할 것으로 예상되는 해당 타입의 유일한 사용 방법입니다.

  • 타입 어노테이션과 타입 별칭 및 캐스트의 대상 타입처럼 임의의 타입 힌트가 유효한 모든 컨텍스트에서 사용할 수 있습니다.
  • 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

위의 사용 사례는 지원되지 않습니다. 이는 isinstance()List[str]에 대해 지원되지 않는 방식과 일관됩니다.

상속

클래스 기반 구문을 사용하면 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의 두 번째 인자로 딕셔너리 표시 표현식을 허용하기만 하면 됩니다. 특히 구현을 단순화하기 위해 딕셔너리 객체를 참조하는 변수를 지원할 필요는 없습니다.

타입 일관성

비공식적으로 말하면, type consistencyAny 타입을 지원하도록 is-subtype-of 관계를 일반화한 것입니다. 이는 PEP 483에서 더 형식적으로 정의되어 있습니다. 이 절에서는 TypedDict 타입에 대한 타입 일관성을 지원하는 데 필요한 새롭고 사소하지 않은 규칙을 소개합니다.

첫째, 모든 TypedDict 타입은 Mapping[str, object]와 일관됩니다. 둘째, TypedDict 타입 AA가 TypedDict B와 구조적으로 호환되는 경우 TypedDict B와 일관됩니다. 이는 다음 두 조건이 모두 충족되는 경우에만 참입니다.

  • B의 각 키에 대해 A에는 해당 키가 있으며, A의 해당 값 타입은 B의 값 타입과 일관됩니다. B의 각 키에 대해 B의 값 타입은 A의 해당 값 타입과도 일관됩니다.
  • B의 각 필수 키에 대해 해당 키는 A에서도 필수입니다. B의 각 비필수 키에 대해 해당 키는 A에서 필수가 아닙니다.

논의:

  • TypedDict 객체는 변경 가능하므로 값 타입은 불변적으로 동작합니다. 이는 ListDict와 같은 변경 가능한 컨테이너 타입과 유사합니다. 이것이 관련되는 예:
    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 값이 있을 수 있기 때문입니다. 예를 들어 이러한 값에는 Mappingvalues()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 타입이 관여하지 않는 연산이 런타임 타입 안전성을 위반할 수 있다면 타입 검사기가 이를 거부해야 한다는 것입니다. 다음은 방지해야 할 가장 중요한 타입 안전성 위반의 일부입니다.

  1. 필수 키가 누락되었습니다.
  2. 값의 타입이 유효하지 않습니다.
  3. TypedDict 타입에 정의되지 않은 키가 추가되었습니다.

리터럴이 아닌 키는 일반적으로 거부해야 합니다. 타입 검사 중에는 해당 키의 값을 알 수 없으므로 위의 일부 위반을 일으킬 수 있기 때문입니다. (Use of Final Values and Literal Types는 이를 최종 이름과 리터럴 타입까지 일반화합니다.)

존재한다고 알려지지 않은 키의 사용은 런타임 타입 오류를 반드시 발생시키지는 않더라도 오류로 보고해야 합니다. 이는 흔히 실수이며, 구조적 서브타이핑이 특정 항목의 타입을 숨기면 잘못된 타입의 값을 삽입할 수 있습니다. 예를 들어, d['x']d에 유효한 키가 아니라면(여기서 d는 TypedDict 타입이라고 가정합니다) 타입 검사 오류가 발생해야 합니다.

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 값과 리터럴 타입의 사용

타입 검사기는 문자열 값을 가진 최종 이름(PEP 591)을 TypedDict 객체에 대한 연산에서 문자열 리터럴 대신 사용할 수 있도록 허용해야 합니다. 예를 들어, 다음은 유효합니다.:

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 타입 정의에서 키를 지정할 때 최종 이름이나 리터럴 타입이 아닌 실제 문자열 리터럴만 지원하면 됩니다. 또한 TypedDict 정의에서 전체 여부를 지정할 때는 불리언 리터럴만 사용할 수 있습니다. 이는 타입 선언을 자체 완결적으로 만들고 타입 검사기의 구현을 단순화하기 위한 것입니다.

하위 호환성

하위 호환성을 유지하려면, 프로그래머가 이를 원한다는 점이 충분히 명확하지 않은 한 타입 검사기는 TypedDict 타입을 추론해서는 안 됩니다. 확실하지 않은 경우에는 일반 딕셔너리 타입을 추론해야 합니다. 그렇지 않으면 딕셔너리 타입보다 TypedDict 타입이 더 제한적이므로, 타입 검사기에 TypedDict 지원이 추가된 후 오류 없이 타입 검사를 통과하던 기존 코드에서 오류가 발생하기 시작할 수 있습니다. 특히 TypedDict 타입은 딕셔너리 타입의 서브타입이 아닙니다.

참조 구현

mypy [2] 타입 검사기는 TypedDict 타입을 지원합니다. 런타임 구성 요소의 참조 구현은 typing_extensions [3] 모듈에서 제공합니다. 원래 구현은 mypy_extensions [4] 모듈에 있었습니다.

거부된 대안

몇 가지 제안된 아이디어가 거부되었습니다. 현재 기능 집합은 많은 부분을 다루는 것으로 보이지만, 제안된 확장 중 어느 것이 사소한 유용성을 넘어설 만큼 유용할지는 명확하지 않았습니다. 이 PEP는 나중에 잠재적으로 확장할 수 있는 기준 기능을 정의합니다.

다음은 이 제안의 취지와 양립할 수 없으므로 원칙적으로 거부되었습니다:

  • TypedDict는 확장 가능하지 않으며, 특정 사용 사례만을 다룹니다. TypedDict 객체는 런타임에 일반 딕셔너리이며, TypedDict는 dict의 서브클래스를 포함하여 다른 딕셔너리형 또는 매핑형 클래스와 함께 사용할 수 없습니다. TypedDict 타입에 메서드를 추가할 방법은 없습니다. 여기서의 동기는 단순성입니다.
  • TypedDict 타입 정의를 사용하여 딕셔너리의 런타임 타입 검사를 수행하는 것도 충분히 가능할 법합니다. 예를 들어, TypedDict 타입으로 지정된 스키마를 JSON 객체가 준수하는지 검증하는 데 사용할 수 있습니다. 이 PEP는 이러한 기능을 포함하지 않습니다. 이 제안의 초점은 정적 타입 검사에만 있으며, Class-based Syntax에서 설명하듯 다른 기존 타입도 이를 지원하지 않기 때문입니다. 예를 들어, 이러한 기능은 typing_inspect [5] 서드파티 모듈을 사용하는 서드파티 라이브러리로 제공할 수 있습니다.
  • TypedDict 타입은 isinstance() 또는 issubclass() 검사에 사용할 수 없습니다. 그 이유는 많은 타입 힌트에서 런타임 타입 검사를 일반적으로 지원하지 않는 이유와 유사합니다.

다음 기능은 이 PEP에서 제외되었지만, 향후 추가할 수 있는 잠재적인 확장입니다:

  • TypedDict는 명시적으로 정의되지 않은 키에 대해 기본값 타입을 제공하는 것을 지원하지 않습니다. 이를 통해 임의의 키를 TypedDict 객체와 함께 사용할 수 있으며, 명시적으로 열거된 키만 일반적인 균일 딕셔너리 타입과 비교하여 특별히 처리할 수 있습니다.
  • 각 키가 필수인지 여부를 개별적으로 지정할 방법은 없습니다. 제안된 구문 중 충분히 명확한 것이 없었으며, 이에 대한 필요성도 제한적일 것으로 예상합니다.
  • TypedDict는 **kwargs 인자의 타입을 지정하는 데 사용할 수 없습니다. 이렇게 하면 허용되는 키워드 인자와 그 타입을 제한할 수 있습니다. TypedDict 타입을 **kwargs의 타입으로 사용하는 것은 PEP 484에 따르면 TypedDict가 임의의 키워드 인자의 으로 유효하다는 의미이지만, 허용되는 키워드 인자를 제한하지는 않습니다. 이를 위해 **kwargs: Expand[T] 구문이 제안되었습니다 [6].

감사의 말

David Foster는 mypy에 TypedDict 타입의 초기 구현을 기여했습니다. 구현 개선에는 최소한 저자(Jukka Lehtosalo), Ivan Levkivskyi, Gareth T, Michael Lee, Dominik Miedzinski, Roy Williams, Max Moroz가 기여했습니다.

참고 문헌