From 221d07cb9b8fcb7c5cce9e11c9ccdf5181f86dfc Mon Sep 17 00:00:00 2001 From: Zixuan Li <39874143+PIG208@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:06:14 -0400 Subject: [PATCH] PEP 728: Address Review Feedback (#3697) --- peps/pep-0728.rst | 62 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/peps/pep-0728.rst b/peps/pep-0728.rst index 13488a18f..d781c44ca 100644 --- a/peps/pep-0728.rst +++ b/peps/pep-0728.rst @@ -252,7 +252,11 @@ be rejected by the type checker. __extra_items__: str # A regular key a: Movie = {"name": "Blade Runner", "__extra_items__": None} # Not OK. 'None' is incompatible with 'str' - b: Movie = {"name": "Blade Runner", "other_extra_key": None} # OK + b: Movie = { + "name": "Blade Runner", + "__extra_items__": "A required regular key", + "other_extra_key": None, + } # OK Here, ``"__extra_items__"`` in ``a`` is a regular key defined on ``Movie`` where its value type is narrowed from ``ReadOnly[str | None]`` to ``str``, @@ -530,6 +534,40 @@ between this type and a closed TypedDict type:: extra_int = not_closed # Not OK. 'ReadOnly[object]' implicitly on 'MovieNotClosed' is not consistent with 'int' for item '__extra_items__' not_closed = extra_int # OK +Interaction with Constructors +----------------------------- + +TypedDicts that allow extra items of type ``T`` also allow arbitrary keyword +arguments of this type when constructed by calling the class object:: + + class OpenMovie(TypedDict): + name: str + + OpenMovie(name="No Country for Old Men") # OK + OpenMovie(name="No Country for Old Men", year=2007) # Not OK. Unrecognized key + + class ExtraMovie(TypedDict, closed=True): + name: str + __extra_items__: int + + ExtraMovie(name="No Country for Old Men") # OK + ExtraMovie(name="No Country for Old Men", year=2007) # OK + ExtraMovie( + name="No Country for Old Men", + language="English", + ) # Not OK. Wrong type for extra key + + # This implies '__extra_items__: Never', + # so extra keyword arguments produce an error + class ClosedMovie(TypedDict, closed=True): + name: str + + ClosedMovie(name="No Country for Old Men") # OK + ClosedMovie( + name="No Country for Old Men", + year=2007, + ) # Not OK. Extra items not allowed + Interaction with Mapping[KT, VT] -------------------------------- @@ -571,14 +609,17 @@ prohibits additional required keys in its structural subtypes, we can determine if the TypedDict type and its structural subtypes will ever have any required key during static analysis. -If there is no required key, the TypedDict type is consistent with ``dict[KT, -VT]`` and vice versa if all items on the TypedDict type satisfy the following -conditions: +The TypedDict type is consistent with ``dict[str, VT]`` if all items on the +TypedDict type satisfy the following conditions: - ``VT`` is consistent with the value type of the item - The value type of the item is consistent with ``VT`` +- The item is not read-only. + +- The item is not required. + For example:: class IntDict(TypedDict, closed=True): @@ -601,6 +642,15 @@ In this case, methods that are previously unavailable on a TypedDict are allowed reveal_type(not_required_num.popitem()) # OK. Revealed type is tuple[str, int] +However, ``dict[str, VT]`` is not necessarily consistent with a TypedDict type, +because such dict can be a subtype of dict:: + + class CustomDict(dict[str, int]): + ... + + not_a_regular_dict: CustomDict = {"num": 1} + int_dict: IntDict = not_a_regular_dict # Not OK + How to Teach this ================= @@ -652,8 +702,8 @@ Supporting ``TypedDict(extra=type)`` ------------------------------------ While this design is potentially viable, there are several partially addressable -concerns to consider. Adding everything up, it is slightly less favorable than -the current proposal. +concerns to consider. The author of this PEP thinks that it is slightly less +favorable than the current proposal. - Usability of forward reference As in the functional syntax, using a quoted type or a type alias will be