

























extra_items or closed)closed behavior (PEP 728)extra_items (PEP 728)ParamSpec and ConcatenateUnpackThis PEP proposes allowing Unpack[TypedDict] in the parameter list inside
Callable, enabling concise and type-safe ways to describe keyword-only
callable signatures. Currently, Callable assumes positional-only
parameters, and typing keyword-only functions requires verbose callback
protocols. With this proposal, the keyword structure defined by a TypedDict
can be reused directly in Callable.
The typing 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 Callback protocols.”
This limitation makes it cumbersome to declare callables meant to be invoked
with keyword arguments. The existing solution is to define a 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: ...
This works but is verbose. The new syntax allows the equivalent to be written more succinctly:
type KwCallable = Callable[[Unpack[KeywordTD]], Any]
The primary goal is to make the common pattern of “callbacks that are intended
to be called with specific keyword arguments” straightforward to express with
Callable. Today, such callbacks must be written as a Protocol with a
__call__ that uses **kwargs: Unpack[...] or includes each keyword
parameter explicitly. This approach is verbose and inconsistent
with how positional variadics are supported: per PEP 646, *args can be
expressed as *tuple[int, ...] inside Callable.
Allowing Unpack[TypedDict] inside Callable achieves the following:
Callable[[...], R] shape while enabling
keyword-only parameter descriptions.__call__(self, **kwargs: Unpack[TD]) -> R.Unpack for **kwargs) and
PEP 728 (extra_items and closed).Protocol-based callbacks only.
This keeps the status quo and avoids changing Callable. However, it is
syntactically heavier and duplicates concepts already present in
Callable.Callable syntax for keywords (e.g., dedicated keyword
parameter markers inside Callable).
This would require extending the callable parameter grammar with new
constructs, creating fresh semantics for optionality, defaults, and extra
keywords. The design space overlaps with TypedDict and PEP 692 and
risks divergent behavior from existing **kwargs typing.Callable and existing typing semantics.Unpack[TD] retains existing Callable semantics and mirrors real
Python functions where positional and keyword-only parameters coexist.Concatenate: Combining Unpack[TD] with Concatenate would enable
interspersed keyword-only parameters among *args and **kwargs. This
increases complexity and is not proposed here.It becomes valid to write:
Callable[[Unpack[TD]], R]
where TD is a TypedDict. A shorter form is also allowed:
Additionally, positional parameters may be combined with an unpacked
TypedDict:
Callable[[int, str, Unpack[TD]], R]
For type-checking purposes, Callable[[Unpack[TD]], R] behaves as if it were
specified via a callback protocol whose __call__ method has
**kwargs: Unpack[TD].
The semantics of Unpack itself are exactly those described in the typing
specification’s Unpack for keyword arguments
section and PEP 692, together with PEP 728 for extra_items and
closed.
This PEP only adds the following Callable-specific rules:
Unpack[TD] may appear inside the parameter list of
Callable.Callable before Unpack[TD] and
follow existing Callable semantics.ParamSpec may be substituted by an unpacked TypedDict within a
Callable.The following examples illustrate how unpacking a TypedDict into a
Callable enforces acceptance of specific keyword parameters. A function is
compatible if it can be called with the required keywords (even if they are
also accepted positionally); positional-only parameters for those keys are
rejected:
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
Keys marked NotRequired in the TypedDict correspond to optional
keyword 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 value
that is compatible; functions that omit the parameter entirely are rejected:
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
closed behavior (PEP 728)If closed=True is specified on the TypedDict, no additional keyword
arguments beyond those declared are expected:
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)
ParamSpec and ConcatenateA ParamSpec can be substituted by Unpack[KeywordTD] to define a
parameterized callable alias. Substituting Unpack[KeywordTD] produces the
same effect as writing the callable with an unpacked TypedDict directly.
Using a TypedDict within Concatenate is not allowed.
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
The current implementation needs to be updated to allow subscripting with a
generic Unpack[TypedDict] without extra brackets;
see Backwards Compatibility.
UnpackPositional parameters may precede an unpacked TypedDict inside Callable.
Functions that accept the required positional arguments and can be called with
the specified keyword(s) are compatible; making the keyword positional-only is
rejected:
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
This feature is mostly an additive typing-only feature. It does not affect
existing code.
Subscripting a ParamSpec with a generic Unpack of a TypedDict is
only backwards compatible when placed inside extra brackets; a TypeAliasType
is not affected by this:
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
This feature is a shorthand for Protocol-based callbacks. Users should be taught that with
class KeywordTD(TypedDict): a: int b: NotRequired[str]
Callable[[Unpack[KeywordTD]], R] is equivalent to defining a Protocol with
__call__(self, **kwargs: Unpack[KeywordTD]) -> R
or
__call__(self, a: int, b: str = ..., **kwargs: object) -> R.TypedDict with
Callable first before introducing Protocol.**kwargs: object might be surprising to users;
using closed=True for definitions will create the more intuitive
equivalence of __call__(self, a: int, b: str = ...) -> Rextra_items from
PEP 728.A prototype exists in mypy: python/mypy#16083.
Unpack[TD] with Concatenate. With such support, one could
write Callable[Concatenate[int, Unpack[TD], P], R] which in turn would
allow a keyword-only parameter between *args and **kwargs, i.e.
def func(*args: Any, a: int, **kwargs: Any) -> R: ...
which is currently not allowed per PEP 612.
To keep the initial implementation simple, this PEP does not propose such
support.TypedDict unpacks be allowed to form a union, and if so,
how to handle overlapping keys of non-identical types? Which restrictions
should apply in such a case? Should the order matter?Callable[Unpack[TD], R] in addition to
Callable[[Unpack[TD]], R]?ReadOnly keys?Thanks to Jelle Zijlstra for sponsoring this PEP and his valuable review feedback.
Hugo van Kemenade, for helpful feedback on the draft and PR of this PEP.
Eric Traut, for feedback on the initial idea and discussions.
Unpack with **kwargsextra_items in TypedDictThis document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。