@@ -187,9 +187,10 @@ determining `supported and unsupported operations
187187The ``extra_items `` Class Parameter
188188-----------------------------------
189189
190- For a TypedDict type that specifies ``extra_items ``, during construction, the
191- value type of each unknown item is expected to be non-required and assignable
192- to the ``extra_items `` argument. For example::
190+ By default ``extra_items `` is unset. For a TypedDict type that specifies
191+ ``extra_items ``, during construction, the value type of each unknown item
192+ is expected to be non-required and assignable to the ``extra_items `` argument.
193+ For example::
193194
194195 class Movie(TypedDict, extra_items=bool):
195196 name: str
@@ -233,15 +234,19 @@ Here, ``'year'`` in ``a`` is an extra key defined on ``Movie`` whose value type
233234is ``int ``. ``'other_extra_key' `` in ``b `` is another extra key whose value type
234235must be assignable to the value of ``extra_items `` defined on ``MovieBase ``.
235236
236- ``extra_items `` is also supported with the functional syntax::
237-
238- Movie = TypedDict("Movie", {"name": str}, extra_items=int | None)
239-
240237.. _typed-dict-closed :
241238
242239The ``closed `` Class Parameter
243240------------------------------
244241
242+ When neither ``extra_items `` nor ``closed=True `` is specified, ``closed=False ``
243+ is assumed. The TypedDict should allow non-required extra items of value type
244+ ``ReadOnly[object] `` during inheritance or assignability checks, to
245+ preserve the default TypedDict behavior. Extra keys included in TypedDict
246+ object construction should still be caught, as mentioned in TypedDict's
247+ `typing spec
248+ <https://typing.python.org/en/latest/spec/typeddict.html#supported-and-unsupported-operations.> `__.
249+
245250When ``closed=True `` is set, no extra items are allowed. This is equivalent to
246251``extra_items=Never ``, because there can't be a value type that is assignable to
247252:class: `~typing.Never `. It is a runtime error to use the ``closed `` and
@@ -275,8 +280,11 @@ child class is also closed::
275280
276281As a consequence of ``closed=True `` being equivalent to ``extra_items=Never ``,
277282the same rules that apply to ``extra_items=Never `` also apply to
278- ``closed=True ``. It is possible to use ``closed=True `` when subclassing if the
279- ``extra_items `` argument is a read-only type::
283+ ``closed=True ``. While they both have the same effect, ``closed=True `` is
284+ preferred over ``extra_items=Never ``.
285+
286+ It is possible to use ``closed=True `` when subclassing if the ``extra_items ``
287+ argument is a read-only type::
280288
281289 class Movie(TypedDict, extra_items=ReadOnly[str]):
282290 pass
@@ -290,11 +298,6 @@ the same rules that apply to ``extra_items=Never`` also apply to
290298This will be further discussed in
291299:ref: `a later section <pep728-inheritance-read-only >`.
292300
293- When neither ``extra_items `` nor ``closed=True `` is specified, the TypedDict
294- is assumed to allow non-required extra items of value type ``ReadOnly[object] ``
295- during inheritance or assignability checks. This preserves the existing behavior
296- of TypedDict.
297-
298301``closed `` is also supported with the functional syntax::
299302
300303 Movie = TypedDict("Movie", {"name": str}, closed=True)
@@ -326,13 +329,21 @@ For type checking purposes, ``Unpack[SomeTypedDict]`` with extra items should be
326329treated as its equivalent in regular parameters, and the existing rules for
327330function parameters still apply::
328331
329- class Movie(TypedDict, extra_items=int):
332+ class MovieNoExtra(TypedDict):
333+ name: str
334+
335+ class MovieExtra(TypedDict, extra_items=int):
330336 name: str
331337
332- def f(**kwargs: Unpack[Movie]) -> None: ...
338+ def f(**kwargs: Unpack[MovieNoExtra]) -> None: ...
339+ def g(**kwargs: Unpack[MovieExtra]) -> None: ...
333340
334341 # Should be equivalent to:
335- def f(*, name: str, **kwargs: int) -> None: ...
342+ def f(*, name: str) -> None: ...
343+ def g(*, name: str, **kwargs: int) -> None: ...
344+
345+ f(name="No Country for Old Men", year=2007) # Not OK. Unrecognized item
346+ g(name="No Country for Old Men", year=2007) # OK
336347
337348Interaction with Read-only Items
338349--------------------------------
@@ -585,8 +596,33 @@ arguments of this type when constructed by calling the class object::
585596 year=2007,
586597 ) # Not OK. Extra items not allowed
587598
588- Interaction with Mapping[KT, VT]
589- --------------------------------
599+ Supported and Unsupported Operations
600+ ------------------------------------
601+
602+ This statement from the `typing spec
603+ <https://typing.python.org/en/latest/spec/typeddict.html#supported-and-unsupported-operations> `__
604+ still holds true.
605+
606+ Operations with arbitrary str keys (instead of string literals or other
607+ expressions with known string values) should generally be rejected.
608+
609+ This means that indexed accesses and assignments with arbitrary keys can still
610+ be rejected even when ``extra_items `` is specified.
611+
612+ Operations that already apply to ``NotRequired `` items should generally also
613+ apply to extra items, following the same rationale from the `typing spec
614+ <https://typing.python.org/en/latest/spec/typeddict.html#supported-and-unsupported-operations> `__:
615+
616+ The exact type checking rules are up to each type checker to decide. In some
617+ cases potentially unsafe operations may be accepted if the alternative is to
618+ generate false positive errors for idiomatic code.
619+
620+ Some operations are allowed due to the TypedDict being
621+ :term: `typing:assignable ` to ``Mapping[str, VT] `` or ``dict[str, VT] ``.
622+ The two following sections will expand on that.
623+
624+ Interaction with Mapping[str, VT]
625+ ---------------------------------
590626
591627A TypedDict type is :term: `typing:assignable ` to a type of the form ``Mapping[str, VT] ``
592628when all value types of the items in the TypedDict
@@ -618,12 +654,12 @@ and ``items()`` on such TypedDict types::
618654 reveal_type(movie.items()) # Revealed type is 'dict_items[str, str]'
619655 reveal_type(movie.values()) # Revealed type is 'dict_values[str, str]'
620656
621- Interaction with dict[KT , VT]
622- -----------------------------
657+ Interaction with dict[str , VT]
658+ ------------------------------
623659
624660Because the presence of ``extra_items `` on a closed TypedDict type
625661prohibits additional required keys in its :term: `typing:structural `
626- :term: `typing: subtypes <subtype> `, we can determine if the TypedDict type and
662+ :term: `subtypes <subtype> `, we can determine if the TypedDict type and
627663its structural subtypes will ever have any required key during static analysis.
628664
629665The TypedDict type is :term: `typing:assignable ` to ``dict[str, VT] `` if all
@@ -708,8 +744,25 @@ been removed in Python 3.13.
708744Because this is a type-checking feature, it can be made available to older
709745versions as long as the type checker supports it.
710746
711- Open Issues
712- ===========
747+ Rejected Ideas
748+ ==============
749+
750+ Use ``@final `` instead of ``closed `` Class Parameter
751+ -----------------------------------------------------
752+
753+ This was discussed `here <https://github.com/python/mypy/issues/7981 >`__.
754+
755+ Quoting a relevant `comment
756+ <https://github.com/python/mypy/issues/7981#issuecomment-2080161813> `__
757+ from Eric Traut:
758+
759+ The @final class decorator indicates that a class cannot be subclassed. This
760+ makes sense for classes that define nominal types. However, TypedDict is a
761+ structural type, similar to a Protocol. That means two TypedDict classes
762+ with different names but the same field definitions are equivalent types.
763+ Their names and hierarchies don't matter for determining type consistency.
764+ For that reason, @final has no impact on a TypedDict type consistency rules,
765+ nor should it change the behavior of items or values.
713766
714767Use a Special ``__extra_items__ `` Key with the ``closed `` Class Parameter
715768-------------------------------------------------------------------------
@@ -725,7 +778,7 @@ where ``closed=True`` is required for ``__extra_items__`` to be treated
725778specially, to avoid key collision.
726779
727780Some members of the community concern about the elegance of the syntax.
728- Practiaclly , the key collision with a regular key can be mitigated with
781+ Practically , the key collision with a regular key can be mitigated with
729782workarounds, but since using a reserved key is central to this proposal,
730783there are limited ways forward to address the concerns.
731784
@@ -767,9 +820,6 @@ types altogether, but there are some disadvantages. `For example
767820- The types don't appear in an annotation context, so their evaluation will
768821 not be deferred.
769822
770- Rejected Ideas
771- ==============
772-
773823Allowing Extra Items without Specifying the Type
774824------------------------------------------------
775825
@@ -827,19 +877,24 @@ For example:
827877 [index : string ]: number | string
828878 }
829879
830- This is a known limitation discussed in `TypeScript's issue tracker
831- <https://github.com/microsoft/TypeScript/issues/17867> `__,
832- where it is suggested that there should be a way to exclude the defined keys
833- from the index signature so that it is possible to define a type like
834- ``MovieWithExtraNumber ``.
880+ While this restriction allows for sound indexed accesses with arbitrary keys,
881+ it comes with usability limitations discussed in `TypeScript's issue tracker
882+ <https://github.com/microsoft/TypeScript/issues/17867> `__.
883+ A suggestion was to allow excluding the defined keys from the index signature,
884+ to define a type like ``MovieWithExtraNumber ``. This probably involves
885+ subtraction types, which is beyond the scope of this PEP.
835886
836887Reference Implementation
837888========================
838889
839- An earlier revision of proposal is supported in `pyright 1.1.352
840- <https://github.com/microsoft/pyright/releases/tag/1.1.352> `_, and `pyanalyze
890+ This is supported in `pyright 1.1.386
891+ <https://github.com/microsoft/pyright/releases/tag/1.1.386> `_, and an earlier
892+ revision is supported in `pyanalyze
8418930.12.0 <https://github.com/quora/pyanalyze/releases/tag/v0.12.0> `_.
842894
895+ This is also supported in `typing-extensions 4.13.0
896+ <https://pypi.org/project/typing-extensions/4.13.0/> `_.
897+
843898Acknowledgments
844899===============
845900
0 commit comments