This document introduces PEP 821:
Currently, we often need to use a more verbose Protocol definition. For example, instead of
class KwCallable(Protocol):
def __call__(self, **kwargs: Unpack[ATypedDict]) -> Any: ...
we would like to write:
Callable[[Unpack[ATypedDict]], Any]
Additionally, this form allows positional parameters to be listed before the unpacked TypedDict:
Callable[[int, str, Unpack[ATypedDict]], Any]
It also supports substituting a ParamSpec with an unpacked TypedDict:
type C[**P] = Callable[P, Any]
C[Unpack[ATypedDict]]
This PEP is intended to follow existing semantics closely: equivalence to the corresponding Protocol definition; **kwargs typing as per PEP 692; and respect for closed and extra_items as defined in PEP 728.
Open Questions
-
The PEP currently proposes to also allow a short form that omits the parameter list as the first argument:
Callable[Unpack[TypedDict], Any] # Callable[[Unpack[TypedDict]], Any]This mirrors the existing exception for
ParamSpecandConcatenate, except that those must appear outside the parameter list.Should we allow this exception to reduce the visual clutter of the additional pair of brackets?
On the other hand, such an exception would require a change to the definition of theparameters_expressionand might increase confusion about when a parameter list is required and when it is not. -
Should multiple
TypedDicts be supported, i.e.
Callable[[Unpack[TD1], Unpack[TD2]], Any]?This would allow combining and merging multiple keyword-parameter definitions, but raises the question of how to interpret them, especially when keys overlap.
I currently see two ways to interpret such a parameter list:2.1. Type checkers treat
[Unpack[TD1], Unpack[TD2]]as a subclass declaration:class _MergeTD(TD2, TD1): ...Later bases would appear earlier in the resolution order (to the extent that this matters), and incompatibilities would result in an error. In short, both
TypedDicts must have compatible key definitions.2.2. A more permissive approach, similar to
{**dict1, **dict2}: keys from the laterTypedDictcompletely overwrite keys from the earlier one. In a--strictmode, they might still be required to be at least partially compatible, e.g. broadening fromint(inTD1) toint | str(inTD2) is acceptable. This could also allow changes in totality.Do you think 2.1 or 2.2 introduces too much complexity for type checker implementations? Which approach would you favor, and why? Is there another approach that would be better?
My current view is that 2.2 is closest to the runtime semantics of dict unpacking and what users might expect. 2.1 might be better expressed using a different form such as intersections or unions, which are currently not defined for
TypedDicts. -
Do any
TypedDict-related special forms, such asReadOnly, require special handling that is not currently covered by this proposal?
This PEP is a follow-up to my idea thread: PEP Idea: Extend spec of Callable to accept unpacked TypeDicts to specify keyword-only parameters