Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 821 – Support for unpacking TypedDicts in Callable type hints

PEP 821 – Support for unpacking TypedDicts in Callable type hints

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

Table of Contents

Abstract

This PEP proposes allowingUnpack[TypedDict] in the parameter list insideCallable, enabling concise and type-safe ways to describe keyword-onlycallable signatures. Currently,Callable assumes positional-onlyparameters, and typing keyword-only functions requires verbose callbackprotocols. With this proposal, the keyword structure defined by aTypedDictcan be reused directly inCallable.

Motivation

Thetyping specification states:

“Parameters specified using Callable are assumed to be positional-only.The Callable form provides no way to specify keyword-only parameters,or default argument values. For these use cases, see the section on Callbackprotocols.”

This limitation makes it cumbersome to declare callables meant to be invokedwith keyword arguments. The existing solution is to define aProtocol:

classKeywordTD(TypedDict,closed=True):a:intclassKwCallable(Protocol):def__call__(self,**kwargs:Unpack[KeywordTD])->Any:...# orclassKwCallable(Protocol):def__call__(self,*,a:int)->Any:...

This works but is verbose. The new syntax allows the equivalent to be writtenmore succinctly:

typeKwCallable=Callable[[Unpack[KeywordTD]],Any]

Rationale

Design goals

The primary goal is to make the common pattern of “callbacks that are intendedto be called with specific keyword arguments” straightforward to express withCallable. Today, such callbacks must be written as aProtocol with a__call__ that uses**kwargs:Unpack[...] or includes each keywordparameter explicitly. This approach is verbose and inconsistentwith how positional variadics are supported: perPEP 646,*args can beexpressed as*tuple[int,...] insideCallable.

AllowingUnpack[TypedDict] insideCallable achieves the following:

  • Preserves the familiarCallable[[...],R] shape while enablingkeyword-only parameter descriptions.
  • Provides a concise shorthand equivalent to Protocol-based callbacks with__call__(self,**kwargs:Unpack[TD])->R.
  • Provides the intuitive analogue to positional variadics.
  • Reuses existing building blocks and keeps semantics predictable. It alignswith existing semantics fromPEP 692 (Unpack for**kwargs) andPEP 728 (extra_items andclosed).
  • Keeps the feature additive, backwards compatible, and stays mostly atyping-specification change only.

Alternatives considered

  1. Continue recommendingProtocol-based callbacks only.This keeps the status quo and avoids changingCallable. However, it issyntactically heavier and duplicates concepts already present inCallable.
  2. Introduce a newCallable syntax for keywords (e.g., dedicated keywordparameter markers insideCallable).This would require extending the callable parameter grammar with newconstructs, creating fresh semantics for optionality, defaults, and extrakeywords. The design space overlaps withTypedDict andPEP 692 andrisks divergent behavior from existing**kwargs typing.
  3. Adopt callback literal syntax (cf.PEP 677) to express rich signaturesinline. Literal syntax can improve readability but introduces a larger,orthogonal change. This PEP seeks a focused, minimal extension that workswithinCallable and existing typing semantics.

Design trade-offs and decisions

  • Positional parameters: Allowing positional parameters to precedeUnpack[TD] retains existingCallable semantics and mirrors realPython functions where positional and keyword-only parameters coexist.
  • Concatenate: CombiningUnpack[TD] withConcatenate would enableinterspersed keyword-only parameters among*args and**kwargs. Thisincreases complexity and is not proposed here.

Specification

New allowed form

It becomes valid to write:

Callable[[Unpack[TD]],R]

whereTD is aTypedDict. A shorter form is also allowed:

Callable[Unpack[TD],R]

Additionally, positional parameters may be combined with an unpackedTypedDict:

Callable[[int,str,Unpack[TD]],R]

Semantics

For type-checking purposes,Callable[[Unpack[TD]],R] behaves as if it werespecified via a callback protocol whose__call__ method has**kwargs:Unpack[TD].The semantics ofUnpack itself are exactly those described in the typingspecification’sUnpack for keyword argumentssection andPEP 692, together withPEP 728 forextra_items andclosed.

This PEP only adds the following Callable-specific rules:

  • Unpack[TD] may appear inside the parameter list ofCallable.
  • Positional parameters may appear inCallable beforeUnpack[TD] andfollow existingCallable semantics.
  • Only aParamSpec may be substituted by an unpackedTypedDict within aCallable.

Examples

The following examples illustrate how unpacking aTypedDict into aCallable enforces acceptance of specific keyword parameters. A function iscompatible if it can be called with the required keywords (even if they arealso accepted positionally); positional-only parameters for those keys arerejected:

fromtypingimportTypedDict,Callable,Unpack,Any,NotRequiredclassKeywordTD(TypedDict):a:inttypeIntKwCallable=Callable[[Unpack[KeywordTD]],Any]defnormal(a:int):...defkw_only(*,a:int):...defpos_only(a:int,/):...defdifferent(bar:int):...f1:IntKwCallable=normal# Acceptedf2:IntKwCallable=kw_only# Acceptedf3:IntKwCallable=pos_only# Rejectedf4:IntKwCallable=different# Rejected

Optional arguments

Keys markedNotRequired in theTypedDict correspond to optionalkeyword arguments.This means that the callable must accept them, but callers may omit them.Functions that accept the keyword argument must also provide a default valuethat is compatible; functions that omit the parameter entirely are rejected:

classOptionalKws(TypedDict):a:NotRequired[int]typeOptCallable=Callable[[Unpack[OptionalKws]],Any]defdefaulted(a:int=1):...defkw_default(*,a:int=1):...defno_params():...defrequired(a:int):...g1:OptCallable=defaulted# Acceptedg2:OptCallable=kw_default# Acceptedg3:OptCallable=no_params# Rejectedg4:OptCallable=required# Rejected

Additional keyword arguments

Default Behavior (noextra_items orclosed)

If theTypedDict does not specifyextra_items orclosed, additionalkeyword arguments are permitted with typeobject.This is the default behavior:

# implies extra_items=objectclassDefaultTD(TypedDict):a:inttypeDefaultCallable=Callable[[Unpack[DefaultTD]],Any]defv_any(**kwargs:object):...defv_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 behavior (PEP 728)

Ifclosed=True is specified on theTypedDict, no additional keywordarguments beyond those declared are expected:

classClosedTD(TypedDict,closed=True):a:inttypeClosedCallable=Callable[[Unpack[ClosedTD]],Any]defv_any(**kwargs:object):...defv_ints(a:int,b:int=2):...c1:ClosedCallable=v_any# Acceptedc1(a=1,c="more")# Rejected (extra c not allowed)c2:ClosedCallable=v_ints# Acceptedc2(a=1,b=2)# Rejected (extra b not allowed)

Interaction withextra_items (PEP 728)

If aTypedDict specifies theextra_items parameter (with the exceptionofextra_items=Never), the correspondingCallablemust accept additional keyword arguments of the specified type.

For example:

classExtraTD(TypedDict,extra_items=str):a:inttypeExtraCallable=Callable[[Unpack[ExtraTD]],Any]defaccepts_str(**kwargs:str):...defaccepts_object(**kwargs:object):...defaccepts_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")# Acceptede1(a=1,b=2)# Rejected (b must be str)

Interaction withParamSpec andConcatenate

AParamSpec can be substituted byUnpack[KeywordTD] to define aparameterized callable alias. SubstitutingUnpack[KeywordTD] produces thesame effect as writing the callable with an unpackedTypedDict directly.Using aTypedDict withinConcatenate is not allowed.

typeCallableP[**P]=Callable[P,Any]h:CallableP[Unpack[KeywordTD]]=normal# Acceptedh2:CallableP[Unpack[KeywordTD]]=kw_only# Acceptedh3:CallableP[Unpack[KeywordTD]]=pos_only# Rejected

The current implementation needs to be updated to allow subscripting with agenericUnpack[TypedDict] without extra brackets;seeBackwards Compatibility.

Combined positional parameters andUnpack

Positional parameters may precede an unpackedTypedDict insideCallable.Functions that accept the required positional arguments and can be called withthe specified keyword(s) are compatible; making the keyword positional-only isrejected:

fromtypingimportTypedDict,Callable,Unpack,AnyclassKeywordTD(TypedDict):a:inttypeIntKwPosCallable=Callable[[int,str,Unpack[KeywordTD]],Any]defmixed_kwonly(x:int,y:str,*,a:int):...defmixed_poskw(x:int,y:str,a:int):...defmixed_posonly(x:int,y:str,a:int,/):...m1:IntKwPosCallable=mixed_kwonly# Acceptedm2:IntKwPosCallable=mixed_poskw# Acceptedm3:IntKwPosCallable=mixed_posonly# Rejected

Backwards Compatibility

This feature is mostly an additive typing-only feature. It does not affectexisting code.Subscripting aParamSpec with a genericUnpack of aTypedDict isonly backwards compatible when placed inside extra brackets; aTypeAliasTypeis not affected by this:

fromtypingimportTypedDict,ParamSpec,Callable,UnpackfromtypingimportTypeAliasTypeclassConfig[T](TypedDict):setting:TtypeCallP1[**P]=Callable[P,None]CallP1_SubbedB=CallP1[Unpack[Config[int]]]# OKP=ParamSpec("P")CallP2=Callable[P,None]CallP2_SubbedA=CallP2[[Unpack[Config[int]]]]# OKCallP2_SubbedB=CallP2[Unpack[Config[int]]]# currently TypeError

How to Teach This

This feature is a shorthand for Protocol-based callbacks. Users should betaught that with

classKeywordTD(TypedDict):a:intb:NotRequired[str]
  • Callable[[Unpack[KeywordTD]],R] is equivalent to defining a Protocol with__call__(self,**kwargs:Unpack[KeywordTD])->Ror__call__(self,a:int,b:str=...,**kwargs:object)->R.
  • Teachers might want to introduce the concept ofTypedDict withCallable first before introducingProtocol.
  • The implicit addition of**kwargs:object might be surprising to users;usingclosed=True for definitions will create the more intuitiveequivalence of__call__(self,a:int,b:str=...)->R
  • Users should be made aware of the interaction withextra_items fromPEP 728.

Reference Implementation

A prototype exists in mypy:python/mypy#16083.

Rejected Ideas

  • CombiningUnpack[TD] withConcatenate. With such support, one couldwriteCallable[Concatenate[int,Unpack[TD],P],R] which in turn wouldallow a keyword-only parameter between*args and**kwargs, i.e.deffunc(*args:Any,a:int,**kwargs:Any)->R:...which is currently not allowed perPEP 612.To keep the initial implementation simple, this PEP does not propose suchsupport.

Open Questions

  • Should multipleTypedDict unpacks be allowed to form a union, and if so,how to handle overlapping keys of non-identical types? Which restrictionsshould apply in such a case? Should the order matter?
  • Should we allow the shorter formCallable[Unpack[TD],R] in addition toCallable[[Unpack[TD]],R]?
  • Is there a necessity to differentiate between normal andReadOnly keys?

Acknowledgements

Thanks to Jelle Zijlstra for sponsoring this PEP and his valuable reviewfeedback.

Hugo van Kemenade, for helpful feedback on the draft and PR of this PEP.

Eric Traut, for feedback on the initial idea and discussions.

References

Copyright

This document is placed in the public domain or under the CC0-1.0-Universallicense, whichever is more permissive.


Source:https://github.com/python/peps/blob/main/peps/pep-0821.rst

Last modified:2026-01-31 20:25:24 GMT


[8]ページ先頭

©2009-2026 Movatter.jp