PEP 821 – 호출 가능 객체 타입 힌트에서 TypedDict 언패킹 지원
- Author:
- Daniel Sperber <github.blurry at 9ox.net>
- Sponsor:
- Jelle Zijlstra <jelle.zijlstra at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Topic:
- Typing
- Created:
- 12-Jan-2026
- Python-Version:
- 3.15
- Post-History:
- 28-Jun-2025, 31-Jan-2026
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 Callable내부의 매개변수 목록에서 Unpack[TypedDict]를 허용하여, 키워드 전용 호출 가능 객체 시그니처를 간결하고 타입 안전하게 설명할 수 있도록 제안합니다. 현재 Callable은 위치 전용 매개변수를 가정하며, 키워드 전용 함수를 타입 지정하려면 장황한 콜백 프로토콜이 필요합니다. 이 제안을 사용하면 TypedDict로 정의된 키워드 구조를 Callable에서 직접 재사용할 수 있습니다.
동기
다음의 typing specification은 다음과 같이 명시합니다:
Callable을 사용하여 지정된 매개변수는 위치 전용 호출 가능 객체로 간주합니다. 호출 가능 객체 형식은 키워드 전용 매개변수나 기본 인자 값을 지정할 방법을 제공하지 않습니다. 이러한 사용 사례는 콜백 프로토콜 섹션을 참조하십시오.”
이러한 제한 때문에 키워드 인자로 호출하도록 의도된 호출 가능 객체를 선언하기가 번거롭습니다. 기존 해결책은 Protocol을 정의하는 것입니다:
class KeywordTD(TypedDict, closed=True):
a: int
class KwCallable(Protocol):
def __call__(self, **kwargs: Unpack[KeywordTD]) -> Any: ...
# or
class KwCallable(Protocol):
def __call__(self, *, a: int) -> Any: ...
이 방법은 작동하지만 장황합니다. 새로운 구문을 사용하면 동등한 내용을 더 간결하게 작성할 수 있습니다:
type KwCallable = Callable[[Unpack[KeywordTD]], Any]
근거
설계 목표
주요 목표는 “특정 키워드 인자로 호출하도록 의도된 콜백”이라는 일반적인 패턴을 Callable로 간단히 표현할 수 있도록 하는 것입니다. 현재 이러한 콜백은 Protocol로 작성하고, **kwargs: Unpack[...]를 사용하는 __call__을 포함하거나 각 키워드 매개변수를 명시적으로 포함해야 합니다. 이 접근 방식은 장황하며 위치 가변 인자가 지원되는 방식과 일관되지 않습니다. PEP 646에 따르면, *args는 Callable내부에서 *tuple[int, ...]로 표현할 수 있습니다.
Callable내부에서 Unpack[TypedDict]를 허용하면 다음을 달성할 수 있습니다.
- 익숙한
Callable[[...], R]형태를 유지하면서 키워드 전용 매개변수 설명을 사용할 수 있습니다. __call__(self, **kwargs: Unpack[TD]) -> R을 사용하는 프로토콜 기반 콜백과 동등한 간결한 축약형을 제공합니다.- 위치 가변 인자에 대한 직관적인 대응을 제공합니다.
- 기존 구성 요소를 재사용하고 의미를 예측 가능하게 유지합니다. 이는 PEP 692 (
Unpackfor**kwargs) 및 PEP 728 (extra_items및closed)의 기존 의미 체계와 일치합니다. - 이 기능은 추가적이고 하위 호환성을 유지하며, 대부분 타입 지정 사양만 변경합니다.
고려한 대안
Protocol기반 콜백만 계속 권장합니다. 이렇게 하면 현재 상태를 유지하고Callable을 변경하지 않을 수 있습니다. 그러나 구문이 더 복잡하고Callable에 이미 존재하는 개념을 중복합니다.- 키워드에 대한 새로운
Callable구문을 도입합니다(예:Callable내부의 전용 키워드 매개변수 표기). 이를 위해 호출 가능 객체 매개변수 문법을 새로운 구성 요소로 확장하고, 선택 사항, 기본값 및 추가 키워드에 대한 새로운 의미를 정의해야 합니다. 이 설계 공간은TypedDict와 PEP 692와 겹치며 기존**kwargs타이핑과 다른 동작이 발생할 위험이 있습니다. - 풍부한 시그니처를 인라인으로 표현하기 위해 콜백 리터럴 구문(PEP 677 참조)을 채택합니다. 리터럴 구문은 가독성을 향상할 수 있지만 더 크고 직교적인 변경을 도입합니다. 이 PEP는
Callable및 기존 타이핑 의미 체계 내에서 작동하는 집중적이고 최소한의 확장을 추구합니다.
설계상의 절충점과 결정
- 위치 매개변수: 위치 매개변수가
Unpack[TD]에 앞서도록 허용하면 기존Callable의미 체계를 유지하고 위치 매개변수와 키워드 전용 매개변수가 함께 존재하는 실제 Python 함수의 형태를 반영합니다. Concatenate:Unpack[TD]와Concatenate를 결합하면*args와**kwargs사이에 키워드 전용 매개변수를 섞어 배치할 수 있습니다. 이는 복잡성을 증가시키므로 여기에서는 제안하지 않습니다.
사양
새로 허용되는 형식
다음과 같이 작성하는 것이 유효해집니다.:
Callable[[Unpack[TD]], R]
여기서 TD는 TypedDict입니다. 더 짧은 형식도 허용됩니다.:
Callable[Unpack[TD], R]
또한 위치 매개변수를 압축 해제된 TypedDict와 결합할 수 있습니다.:
Callable[[int, str, Unpack[TD]], R]
의미 체계
타입 검사 목적에서 Callable[[Unpack[TD]], R]는 **kwargs: Unpack[TD]를 갖는 __call__ 메서드가 있는 콜백 프로토콜을 통해 지정한 것처럼 동작합니다. Unpack자체의 의미 체계는 타이핑 사양의 Unpack for keyword arguments 섹션과 PEP 692에 설명된 내용 및 extra_items와 closed에 관한 PEP 728에 설명된 내용을 정확히 따릅니다.
이 PEP는 다음과 같은 호출 가능 객체별 규칙만 추가합니다:
Unpack[TD]는Callable의 매개변수 목록 안에 나타날 수 있습니다.- 위치 매개변수는
Callable에서Unpack[TD]앞에 나타날 수 있으며 기존Callable의미 체계를 따릅니다. Callable내부에서 압축 해제된TypedDict로 대체될 수 있는 것은ParamSpec뿐입니다.
예제
다음 예제는 TypedDict를 Callable로 압축 해제할 때 특정 키워드 매개변수의 수용이 어떻게 강제되는지 보여 줍니다. 함수는 필수 키워드로 호출할 수 있으면 호환됩니다(해당 키워드가 위치 인자로도 허용되는 경우 포함). 해당 키에 대한 위치 전용 매개변수는 거부됩니다.:
from typing import TypedDict, Callable, Unpack, Any, NotRequired
class KeywordTD(TypedDict):
a: int
type IntKwCallable = Callable[[Unpack[KeywordTD]], Any]
def normal(a: int): ...
def kw_only(*, a: int): ...
def pos_only(a: int, /): ...
def different(bar: int): ...
f1: IntKwCallable = normal # Accepted
f2: IntKwCallable = kw_only # Accepted
f3: IntKwCallable = pos_only # Rejected
f4: IntKwCallable = different # Rejected
선택적 인자
TypedDict에서 NotRequired로 표시된 키는 선택적 키워드 인자에 해당합니다. 이는 호출 가능 객체가 해당 인자를 수용해야 하지만 호출자는 이를 생략할 수 있음을 의미합니다. 키워드 인자를 수용하는 함수는 호환되는 기본값도 제공해야 하며, 매개변수를 완전히 생략하는 함수는 거부됩니다.:
class OptionalKws(TypedDict):
a: NotRequired[int]
type OptCallable = Callable[[Unpack[OptionalKws]], Any]
def defaulted(a: int = 1): ...
def kw_default(*, a: int = 1): ...
def no_params(): ...
def required(a: int): ...
g1: OptCallable = defaulted # Accepted
g2: OptCallable = kw_default # Accepted
g3: OptCallable = no_params # Rejected
g4: OptCallable = required # Rejected
추가 키워드 인자
기본 동작(extra_items나 closed가 없는 경우)
TypedDict에 extra_items 또는 closed가 지정되지 않은 경우, object형의 추가 키워드 인자가 허용됩니다. 이것이 기본 동작입니다.:
# implies extra_items=object
class DefaultTD(TypedDict):
a: int
type DefaultCallable = Callable[[Unpack[DefaultTD]], Any]
def v_any(**kwargs: object): ...
def v_ints(a: int, b: int=2): ...
d1: DefaultCallable = v_any # Accepted (implicit object for extras)
d1(a=1, c="more") # Accepted (extras allowed)
d2: DefaultCallable = v_ints # Rejected (b: int is not a supertype of object)
closed 동작 (PEP 728)
TypedDict에 closed=True가 지정된 경우, 선언된 것 이외의 추가 키워드 인자는 예상되지 않습니다.:
class ClosedTD(TypedDict, closed=True):
a: int
type ClosedCallable = Callable[[Unpack[ClosedTD]], Any]
def v_any(**kwargs: object): ...
def v_ints(a: int, b: int=2): ...
c1: ClosedCallable = v_any # Accepted
c1(a=1, c="more") # Rejected (extra c not allowed)
c2: ClosedCallable = v_ints # Accepted
c2(a=1, b=2) # Rejected (extra b not allowed)
extra_items와의 상호 작용 (PEP 728)
TypedDict에 extra_items 매개변수가 지정된 경우(extra_items=Never는 제외), 해당 Callable은 지정된 형의 추가 키워드 인자를 받아들여야 합니다.
예를 들어:
class ExtraTD(TypedDict, extra_items=str):
a: int
type ExtraCallable = Callable[[Unpack[ExtraTD]], Any]
def accepts_str(**kwargs: str): ...
def accepts_object(**kwargs: object): ...
def accepts_int(**kwargs: int): ...
e1: ExtraCallable = accepts_str # Accepted (matches extra_items type)
e2: ExtraCallable = accepts_object # Accepted (object is a supertype of str)
e3: ExtraCallable = accepts_int # Rejected (int is not a supertype of str)
e1(a=1, b="foo") # Accepted
e1(a=1, b=2) # Rejected (b must be str)
ParamSpec과 Concatenate의 상호 작용
ParamSpec은 매개변수화된 호출 가능 객체 별칭을 정의하기 위해 Unpack[KeywordTD]로 대체할 수 있습니다. Unpack[KeywordTD]로 대체하면 압축을 푼 TypedDict로 호출 가능 객체를 직접 작성하는 것과 동일한 효과가 발생합니다. Concatenate내부에서 TypedDict를 사용하는 것은 허용되지 않습니다.
type CallableP[**P] = Callable[P, Any]
h: CallableP[Unpack[KeywordTD]] = normal # Accepted
h2: CallableP[Unpack[KeywordTD]] = kw_only # Accepted
h3: CallableP[Unpack[KeywordTD]] = pos_only # Rejected
현재 구현은 추가 괄호 없이 일반 Unpack[TypedDict]를 첨자 표기할 수 있도록 업데이트해야 합니다. Backwards Compatibility를 참조하십시오.
위치 매개변수와 Unpack의 결합
Callable내부에서 위치 매개변수는 압축을 푼 TypedDict에 선행할 수 있습니다. 필수 위치 인자를 받아들이고 지정된 키워드(들)로 호출할 수 있는 함수는 호환됩니다. 해당 키워드를 위치 전용으로 만드는 것은 거부됩니다.:
from typing import TypedDict, Callable, Unpack, Any
class KeywordTD(TypedDict):
a: int
type IntKwPosCallable = Callable[[int, str, Unpack[KeywordTD]], Any]
def mixed_kwonly(x: int, y: str, *, a: int): ...
def mixed_poskw(x: int, y: str, a: int): ...
def mixed_posonly(x: int, y: str, a: int, /): ...
m1: IntKwPosCallable = mixed_kwonly # Accepted
m2: IntKwPosCallable = mixed_poskw # Accepted
m3: IntKwPosCallable = mixed_posonly # Rejected
하위 호환성
이 기능은 대부분 추가적인 타이핑 전용 기능입니다. 기존 코드에는 영향을 주지 않습니다. TypedDict의 일반 Unpack으로 ParamSpec을 첨자 표기하는 것은 추가 괄호 안에 배치한 경우에만 하위 호환됩니다. TypeAliasType은 이에 영향을 받지 않습니다.:
from typing import TypedDict, ParamSpec, Callable, Unpack
from typing import TypeAliasType
class Config[T](TypedDict):
setting: T
type CallP1[**P] = Callable[P, None]
CallP1_SubbedB = CallP1[Unpack[Config[int]]] # OK
P = ParamSpec("P")
CallP2 = Callable[P, None]
CallP2_SubbedA = CallP2[[Unpack[Config[int]]]] # OK
CallP2_SubbedB = CallP2[Unpack[Config[int]]] # currently TypeError
이 내용을 가르치는 방법
이 기능은 프로토콜 기반 콜백의 축약형입니다. 사용자에게는 다음과 같은 경우를 가르쳐야 합니다.
class KeywordTD(TypedDict):
a: int
b: NotRequired[str]
Callable[[Unpack[KeywordTD]], R]은__call__(self, **kwargs: Unpack[KeywordTD]) -> R또는__call__(self, a: int, b: str = ..., **kwargs: object) -> R를 사용하는 프로토콜을 정의하는 것과 동등합니다.- 교사는
Protocol을 소개하기 전에 먼저Callable과 함께TypedDict의 개념을 소개할 수 있습니다. **kwargs: object를 암묵적으로 추가하는 것은 사용자에게 놀라울 수 있습니다. 정의에closed=True를 사용하면 보다 직관적으로__call__(self, a: int, b: str = ...) -> R와 동등해집니다.- 사용자에게 PEP 728의
extra_items와의 상호 작용을 알려야 합니다.
참조 구현
mypy에 프로토타입이 있습니다: python/mypy#16083.
거부된 아이디어
Unpack[TD]와Concatenate의 결합 이러한 지원이 있으면Callable[Concatenate[int, Unpack[TD], P], R]를 작성할 수 있으며, 이는 결과적으로*args와**kwargs사이에 키워드 전용 매개변수를 허용하게 됩니다. 즉,def func(*args: Any, a: int, **kwargs: Any) -> R: ...와 같이 작성할 수 있게 되지만, 이는 현재 PEP 612에 따라 허용되지 않습니다. 초기 구현을 단순하게 유지하기 위해 이 PEP에서는 이러한 지원을 제안하지 않습니다.
미해결 질문
- 여러
TypedDict언패킹이 유니온을 형성하도록 허용해야 합니까? 그렇다면 동일하지 않은 타입의 겹치는 키는 어떻게 처리해야 합니까? 이러한 경우 어떤 제한을 적용해야 합니까? 순서가 중요해야 합니까? Callable[[Unpack[TD]], R]에 더해 더 짧은 형식인Callable[Unpack[TD], R]도 허용해야 합니까?- 일반 키와
ReadOnly키를 구분해야 할 필요가 있습니까?
감사의 말
이 PEP를 후원하고 귀중한 검토 피드백을 제공해 주신 Jelle Zijlstra에게 감사드립니다.
이 PEP의 초안과 PR에 유용한 피드백을 제공해 주신 Hugo van Kemenade에게 감사드립니다.
초기 아이디어와 논의에 피드백을 제공해 주신 Eric Traut에게 감사드립니다.
참고 문헌
- PEP 692 -
Unpack을**kwargs와 함께 사용하기 - PEP 728 -
TypedDict의extra_items - mypy PR #16083 - Prototype support
- PEP 677 다시 살펴보기 (discussion thread)
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.