PEP 764 – 인라인 타입 딕셔너리
- Author:
- Victorien Plot <contact at vctrn.dev>
- Sponsor:
- Eric Traut <erictr at microsoft.com>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Topic:
- Typing
- Created:
- 25-Oct-2024
- Python-Version:
- 3.15
- Post-History:
- 29-Jan-2025
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
PEP 589는 타입 딕셔너리를 생성하기 위한 클래스 기반 구문과 기능적 구문을 정의합니다. 두 방식 모두 클래스 정의 또는 값 할당이 필요합니다. 일부 상황에서는 특히 타입 딕셔너리를 한 번만 사용하는 경우 불필요한 상용구가 추가될 수 있습니다.
이 PEP에서는 TypedDict 타입에 첨자를 사용하여 새로운 인라인 구문을 추가할 것을 제안합니다.:
from typing import TypedDict
def get_movie() -> TypedDict[{'name': str, 'year': int}]:
return {
'name': 'Blade Runner',
'year': 1982,
}
동기
Python 딕셔너리는 이 언어의 필수적인 데이터 구조입니다. 함수에서 구조화된 데이터를 반환하거나 받는 데 자주 사용됩니다. 그러나 TypedDict 클래스를 정의하는 작업은 번거로울 수 있습니다.
- 타입 딕셔너리에는 이름이 필요하지만, 그 이름이 중요하지 않을 수도 있습니다.
- 중첩 딕셔너리에는 두 개 이상의 클래스 정의가 필요합니다.
일부 중첩된 구조화 데이터를 반환하는 간단한 함수를 예로 들어 보겠습니다.:
from typing import TypedDict
class ProductionCompany(TypedDict):
name: str
location: str
class Movie(TypedDict):
name: str
year: int
production: ProductionCompany
def get_movie() -> Movie:
return {
'name': 'Blade Runner',
'year': 1982,
'production': {
'name': 'Warner Bros.',
'location': 'California',
}
}
근거
새로운 인라인 구문을 사용하면 이러한 문제를 해결할 수 있습니다.:
def get_movie() -> TypedDict[{'name': str, 'year': int, 'production': TypedDict[{'name': str, 'location': str}]}]:
...
기능적 구문이나 클래스 기반 구문을 사용할 수도 있으므로 유용성이 낮기는 하지만, 인라인 타입 딕셔너리를 별칭으로 변수에 할당할 수 있습니다.:
InlineTD = TypedDict[{'name': str}]
def get_movie() -> InlineTD:
...
사양
이 TypedDict 특수 형식은 서브스크립트 가능하게 만들며, 하나의 타입 인자를 받습니다. 이 타입 인자는 dict여야 하며, 기능적 구문과 동일한 의미 체계를 따릅니다(딕셔너리 키는 필드 이름을 나타내는 문자열이고, 값은 유효한 어노테이션 표현식입니다). 중괄호 생성자 안의 쉼표로 구분된 key: value 쌍 목록({k: <type>})만 허용되며, 타입 인자로 직접 지정해야 합니다(즉, 이전에 dict 인스턴스를 할당한 변수를 사용할 수 없습니다).
인라인 타입 딕셔너리는 특정 이름이 없다는 의미에서 익명이라고 할 수 있습니다(Runtime behavior 섹션을 참조하십시오).
중첩된 인라인 딕셔너리를 정의할 수 있습니다.:
Movie = TypedDict[{'name': str, 'production': TypedDict[{'location': str}]}]
# Note that the following is invalid as per the updated `type_expression` grammar:
Movie = TypedDict[{'name': str, 'production': {'location': str}}]
total과 같은 클래스 인자를 지정할 수는 없지만, 모든 type qualifier를 개별 필드에 사용할 수 있습니다.:
Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}]
인라인 타입 딕셔너리는 암시적으로 전체이므로 모든 키가 존재해야 합니다. 따라서 Required 타입 한정자를 사용하는 것은 중복됩니다.
타입 변수가 일부 외부 스코프에 바인딩되어 있다면 인라인 타입 딕셔너리에서 사용할 수 있습니다.:
class C[T]:
inline_td: TypedDict[{'name': T}] # OK, `T` is scoped to the class `C`.
reveal_type(C[int]().inline_td['name']) # Revealed type is 'int'
def fn[T](arg: T) -> TypedDict[{'name': T}]: ... # OK: `T` is scoped to the function `fn`.
reveal_type(fn('a')['name']) # Revealed type is 'str'
type InlineTD[T] = TypedDict[{'name': T}] # OK, `T` is scoped to the type alias.
T = TypeVar('T')
InlineTD = TypedDict[{'name': T}] # OK, same as the previous type alias, but using the old-style syntax.
def func():
InlineTD = TypedDict[{'name': T}] # Not OK: `T` refers to a type variable that is not bound to the scope of `func`.
인라인 타입 딕셔너리를 확장할 수 있습니다.:
InlineTD = TypedDict[{'a': int}]
class SubTD(InlineTD):
pass
타이핑 사양 변경 사항
인라인 타입 딕셔너리는 새로운 종류의 type expression을 추가합니다. 따라서 type_expression 생성 규칙은 인라인 구문을 포함하도록 업데이트됩니다.
new-type_expression ::=type_expression| <TypedDict> '[' '{' (string: ':'annotation_expression',')* '}' ']' (where string is any string literal)
런타임 동작
인라인 타입 딕셔너리를 생성하면 새로운 클래스가 만들어지므로 T1과 T2는 동일한 타입입니다.:
from typing import TypedDict
T1 = TypedDict('T1', {'a': int})
T2 = TypedDict[{'a': int}]
인라인 타입 딕셔너리는 익명으로 의도되었으므로 해당 __name__ 속성은 <inline TypedDict> 문자열 리터럴로 설정됩니다. 향후 이름이 지정된 클래스와 구별할 수 있도록 명시적인 클래스 속성을 추가할 수 있습니다.
문서에서는 TypedDict를 클래스로 설명하지만, 정의 방식은 구현 세부 사항입니다. 구현을 조정하여 TypedDict가 서브스크립트 가능하도록 만들어야 합니다.
하위 호환성
이 PEP는 하위 호환성을 깨는 변경 사항을 도입하지 않습니다.
보안 영향
이 PEP에서 비롯되는 알려진 보안 관련 결과는 없습니다.
이 내용을 가르치는 방법
새로운 인라인 구문은 typing 모듈 문서와 typing specification 모두에 문서화됩니다.
복잡한 딕셔너리 구조를 사용할 때 모든 내용을 한 줄에 정의하면 가독성이 저하될 수 있습니다. 코드 포매터는 인라인 타입 딕셔너리를 여러 줄에 걸쳐 서식 지정하여 도움을 줄 수 있습니다:
def edit_movie(
movie: TypedDict[{
'name': str,
'year': int,
'production': TypedDict[{
'location': str,
}],
}],
) -> None:
...
참조 구현
Mypy는 experimental feature로 유사한 구문을 지원합니다:
def test_values() -> {"int": int, "str": str}:
return {"int": 42, "str": "test"}
이 PEP에 대한 지원이 this pull request에서 추가됩니다.
Pyright는 1.1.387 버전에서 새로운 구문에 대한 지원을 추가했습니다.
런타임 구현
필요한 변경 사항은 먼저 typing_extensions에서 this pull request에 구현되었습니다.
거부된 아이디어
어노테이션에서 함수형 구문 사용
대체 함수형 구문을 어노테이션으로 직접 사용할 수 있습니다:
def get_movie() -> TypedDict('Movie', {'title': str}): ...
그러나 호출 표현식은 여러 가지 이유로 이러한 컨텍스트에서 현재 지원되지 않습니다(처리 비용이 많이 들고, 평가 방식이 표준화되어 있지 않음).
또한 때로는 관련이 없는 이름이 필요합니다.
단일 타입 인자와 함께 dict 또는 typing.Dict 사용
동일한 개념을 표현하기 위해 단일 타입 인자와 함께 dict 또는 typing.Dict를 재사용할 수 있습니다:
def get_movie() -> dict[{'title': str}]: ...
이렇게 하면 typing에서 TypedDict를 가져올 필요가 없지만, 이 방법에는 몇 가지 단점이 있습니다.
- 타입 검사기에서
dict는 두 개의 타입 변수를 가진 일반 클래스입니다. 단일 타입 인자로dict를 매개변수화하도록 허용하면 타입 검사기에서 별도의 특수 처리가 필요합니다. 매개변수화 오버로드를 표현할 방법이 없기 때문입니다. 반면TypedDict는 이미 special form입니다. - 향후 작업에서 인라인 타입 딕셔너리의 기능이 확장되더라도
dict와 기호를 공유하는 데 따른 영향을 걱정할 필요가 없습니다. typing.Dict는 PEP 585에 의해 더 이상 사용되지 않도록 지정되었습니다(제거할 계획은 없지만). 이를 새로운 타이핑 기능에 사용하면 사용자에게 혼란을 줄 수 있으며 코드 린터를 변경해야 합니다.
간단한 딕셔너리 사용
관련 TypedDict 클래스를 서브스크립팅하는 대신 일반 딕셔너리를 어노테이션으로 사용할 수 있습니다.:
def get_movie() -> {'title': str}: ...
그러나 PEP 584은 딕셔너리에 유니온 연산자를 추가했고, PEP 604은 union types을 도입했습니다. 두 기능 모두 bitwise or (|) 연산자를 사용하므로, 다음 사용 사례와 호환되지 않으며 특히 런타임 인트로스펙션에서 문제가 됩니다.:
# Dictionaries are merged:
def fn() -> {'a': int} | {'b': str}: ...
# Raises a type error at runtime:
def fn() -> {'a': int} | int: ...
다른 타입 딕셔너리 확장
다른 타입 딕셔너리를 확장할 수 있도록 하는 데 여러 문법을 사용할 수 있습니다.:
InlineBase = TypedDict[{'a': int}]
Inline = TypedDict[InlineBase, {'b': int}]
# or, by providing a slice:
Inline = TypedDict[{'b': int} : (InlineBase,)]
인라인 타입 딕셔너리는 기존 문법의 일부만 지원하도록 의도되었으므로, 추가되는 복잡성을 고려하면 이 확장 메커니즘은 지원할 만큼 충분히 설득력이 있지 않습니다.
교차 타입이 타입 시스템에 추가된다면 이 사용 사례를 처리할 수 있습니다.
미해결 문제
인라인 타입 딕셔너리와 추가 항목
PEP 728은 closed 타입 딕셔너리라는 개념을 도입합니다. 이 PEP가 승인되면 인라인 타입 딕셔너리는 기본적으로 닫힌 상태가 됩니다. 이는 이 PEP를 그에 맞게 업데이트할 수 있도록 먼저 PEP 728을 다루어야 한다는 의미입니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.