11PEP: 821
22Title: Support for unpacking TypedDicts in Callable type hints
33Author: Daniel Sperber <github.blurry@9ox.net>
4+ Sponsor: Pending
45Discussions-To: Pending
56Status: Draft
67Type: Standards Track
78Topic: Typing
89Created: 30-Dec-2025
910Python-Version: 3.15
10- Post-History: `28-Jun-2025 <https://discuss.python.org/t/pep-idea-extend-spec-of-callable-to-accept-unpacked-typedicts-to-specify-keyword-only-parameters/96975 >`__,
11-
11+ Post-History: `28-Jun-2025 <https://discuss.python.org/t/pep-idea-extend-spec-of-callable-to-accept-unpacked-typedicts-to-specify-keyword-only-parameters/96975 >`__
1212
1313Abstract
1414========
@@ -53,6 +53,16 @@ more succinctly::
5353 type KwCallable = Callable[[Unpack[Signature]], Any]
5454
5555
56+ Rationale
57+ =========
58+
59+ This proposal extends the existing Callable semantics by reusing a ``TypedDict ``'s
60+ keyed structure for keyword arguments. It avoids verbose Protocol-based
61+ callable definitions while remaining compatible with current typing concepts
62+ (:pep: `692 ` Unpack for ``kwargs ``, and :pep: `728 ` ``extra_items ``). It preserves backward
63+ compatibility by being purely a typing feature.
64+
65+
5666Specification
5767=============
5868
@@ -80,6 +90,7 @@ Semantics
8090* Required keys must be accepted, but may correspond to parameters with a
8191 default value.
8292* ``NotRequired `` keys must still be accepted, but may be omitted at call sites.
93+ This respectively applies to ``TypedDict `` with ``total=False ``.
8394* Functions with ``**kwargs `` are compatible if the annotation of ``**kwargs ``
8495 matches or is a supertype of the ``TypedDict `` values.
8596* ``extra_items `` from PEP 728 is respected: functions accepting additional
@@ -125,11 +136,11 @@ rejected.
125136 Optional arguments
126137------------------
127138
128- Keys marked ``NotRequired `` in the ``TypedDict `` correspond to optional
129- keyword arguments.
130- Meaning the callable must accept them, but callers may omit them.
131- Functions that accept the keyword argument must also provide a default value that is compatible;
132- functions that omit the parameter entirely are rejected.
139+ Keys marked ``NotRequired `` in the ``TypedDict `` correspond to optional
140+ keyword arguments.
141+ Meaning the callable must accept them, but callers may omit them.
142+ Functions that accept the keyword argument must also provide a default value that is compatible;
143+ functions that omit the parameter entirely are rejected.
133144
134145.. code-block :: python
135146
@@ -196,7 +207,7 @@ arguments beyond those declared are expected.
196207 Interaction with ``extra_items `` (PEP 728)
197208~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
198209
199- If a ``TypedDict `` specifies the ``extra_items `` parameter, the corresponding ``Callable ``
210+ If a ``TypedDict `` specifies the ``extra_items `` parameter (with the exemption of `` extra_items=Never ``) , the corresponding ``Callable ``
200211must accept additional keyword arguments of the specified type.
201212
202213For example:
@@ -220,11 +231,13 @@ For example:
220231 e1(a = 1 , b = 2 ) # Rejected (b must be str)
221232
222233
223- Interaction with ParamSpec:
234+ Interaction with ``ParamSpec `` and ``Concatenate ``
235+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
224236
225- ``ParamSpec `` can be combined with ``Unpack[TypedDict] `` to define a
237+ A ``ParamSpec `` can be substituted by ``Unpack[TypedDict] `` to define a
226238parameterized callable alias. Substituting ``Unpack[Signature] `` produces the
227239same effect as writing the callable with an unpacked ``TypedDict `` directly.
240+ Using a ``TypedDict `` within ``Concatenate `` is not allowed.
228241
229242.. code-block :: python
230243
@@ -278,26 +291,6 @@ Backwards Compatibility
278291This feature is additive. Existing code is unaffected. Runtime behavior does
279292not change; this is a typing-only feature.
280293
281-
282- Reference Implementation
283- ========================
284-
285- A prototype exists in mypy:
286- https://github.com/python/mypy/pull/16083
287-
288- Open Questions
289- ==============
290-
291- * Should combining ``Unpack[TD] `` with ``Concatenate `` and ``ParamSpec `` be
292- supported in the future? With such support, one could write
293- ``Callable[Concatenate[int, Unpack[TD], P], R] `` which in turn would allow a keyword-only parameter between ``*args `` and ``**kwargs ``, i.e.
294- ``def func(*args: Any, a: int, **kwargs: Any) -> R: ... `` which is currently not allowed per PEP 612.
295- To keep the initial implementation simple, this PEP does not propose such
296- support.
297- * Should multiple ``TypedDict `` unpacks be allowed to form a union, and if so, how to handle
298- overlapping keys?
299-
300-
301294How to Teach This
302295=================
303296
@@ -317,24 +310,45 @@ taught that with
317310 using ``closed=True `` for definitions will create the more intuitive equivalence
318311 of ``__call__(self, a: int, b: str = ...) -> R ``
319312
320- Alternatives
321- ============
322313
323- This (`discussion thread <https://discuss.python.org/t/pep-677-with-an-easier-to-parse-and-more-expressive-syntax/98408/33 >`__)
324- revisits the idea of the rejected :pep: `677 `
325- to introduce alternative and more expressive syntax for callable type hints.
314+ Reference Implementation
315+ ========================
316+
317+ A prototype exists in mypy:
318+ https://github.com/python/mypy/pull/16083
319+
320+
321+ Rejected Ideas
322+ ==============
323+
324+ - Combining ``Unpack[TD] `` with ``Concatenate ``. With such support, one could write
325+ ``Callable[Concatenate[int, Unpack[TD], P], R] `` which in turn would allow a keyword-only parameter between ``*args `` and ``**kwargs ``, i.e.
326+ ``def func(*args: Any, a: int, **kwargs: Any) -> R: ... `` which is currently not allowed per :pep: `612 `.
327+ To keep the initial implementation simple, this PEP does not propose such
328+ support.
329+
330+ Open Questions
331+ ==============
332+
333+ * Should multiple ``TypedDict `` unpacks be allowed to form a union, and if so, how to handle
334+ overlapping keys of non-identical types? Which restrictions should apply in such a case? Should the order matter?
335+ * Is there a necessity to differentiate between normal and ``ReadOnly `` keys?
336+ * Is it necessary to specify generic behavior for ``TypedDict `` and the resulting ``Callable `` when the ``TypedDict `` itself is generic?
337+
338+
339+ Acknowledgements
340+ ================
341+ TODO
342+
326343
327344
328345References
329346==========
330347
331- * PEP 484 - Type Hints
332- * PEP 612 - ParamSpec
333- * PEP 646 - Variadic Generics
334- * PEP 692 - Using ``Unpack `` with ``**kwargs ``
335- * PEP 728 - ``extra_items `` in TypedDict
336- * PEP 764 - Inline TypedDict
337- * mypy PR #16083 - Prototype support
348+ * :pep: `692 ` - Using ``Unpack `` with ``**kwargs ``
349+ * :pep: `728 ` - ``extra_items `` in TypedDict
350+ * :pep: `764 ` - Inline TypedDict
351+ * `mypy PR #16083 - Prototype support <https://github.com/python/mypy/pull/16083 >`__
338352* Revisiting PEP 677 (`discussion thread <https://discuss.python.org/t/pep-677-with-an-easier-to-parse-and-more-expressive-syntax/98408/33 >`__)
339353
340354
0 commit comments