@@ -187,9 +187,10 @@ determining `supported and unsupported operations
187
187
The ``extra_items `` Class Parameter
188
188
-----------------------------------
189
189
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::
193
194
194
195
class Movie(TypedDict, extra_items=bool):
195
196
name: str
@@ -233,15 +234,14 @@ Here, ``'year'`` in ``a`` is an extra key defined on ``Movie`` whose value type
233
234
is ``int ``. ``'other_extra_key' `` in ``b `` is another extra key whose value type
234
235
must be assignable to the value of ``extra_items `` defined on ``MovieBase ``.
235
236
236
- ``extra_items `` is also supported with the functional syntax::
237
-
238
- Movie = TypedDict("Movie", {"name": str}, extra_items=int | None)
239
-
240
237
.. _typed-dict-closed :
241
238
242
239
The ``closed `` Class Parameter
243
240
------------------------------
244
241
242
+ When neither ``extra_items `` nor ``closed=True `` is specified, ``closed=False ``
243
+ is assumed.
244
+
245
245
When ``closed=True `` is set, no extra items are allowed. This is equivalent to
246
246
``extra_items=Never ``, because there can't be a value type that is assignable to
247
247
:class: `~typing.Never `. It is a runtime error to use the ``closed `` and
@@ -275,8 +275,11 @@ child class is also closed::
275
275
276
276
As a consequence of ``closed=True `` being equivalent to ``extra_items=Never ``,
277
277
the 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::
278
+ ``closed=True ``. While they both have the same effect, ``closed=True `` is
279
+ preferred over ``extra_items=Never ``.
280
+
281
+ It is possible to use ``closed=True `` when subclassing if the ``extra_items ``
282
+ argument is a read-only type::
280
283
281
284
class Movie(TypedDict, extra_items=ReadOnly[str]):
282
285
pass
@@ -290,10 +293,12 @@ the same rules that apply to ``extra_items=Never`` also apply to
290
293
This will be further discussed in
291
294
:ref: `a later section <pep728-inheritance-read-only >`.
292
295
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.
296
+ The TypedDict should allow non-required extra items of value type
297
+ ``ReadOnly[object] `` during inheritance or assignability checks, to
298
+ preserve the default TypedDict behavior. Extra keys included in TypedDict
299
+ object construction should still be caught, as mentioned in TypedDict's
300
+ `typing spec
301
+ <https://typing.python.org/en/latest/spec/typeddict.html#supported-and-unsupported-operations.> `__.
297
302
298
303
``closed `` is also supported with the functional syntax::
299
304
@@ -585,8 +590,33 @@ arguments of this type when constructed by calling the class object::
585
590
year=2007,
586
591
) # Not OK. Extra items not allowed
587
592
588
- Interaction with Mapping[KT, VT]
589
- --------------------------------
593
+ Supported and Unsupported Operations
594
+ ------------------------------------
595
+
596
+ This statement from the `typing spec
597
+ <https://typing.python.org/en/latest/spec/typeddict.html#supported-and-unsupported-operations> `__
598
+ still holds true.
599
+
600
+ Operations with arbitrary str keys (instead of string literals or other
601
+ expressions with known string values) should generally be rejected.
602
+
603
+ This means that indexed accesses and assignments with arbitrary keys can still
604
+ be rejected even when ``extra_items `` is specified.
605
+
606
+ Operations that already apply to ``NotRequired `` items should generally also
607
+ apply to extra items, following the same rationale from the `typing spec
608
+ <https://typing.python.org/en/latest/spec/typeddict.html#supported-and-unsupported-operations> `__:
609
+
610
+ The exact type checking rules are up to each type checker to decide. In some
611
+ cases potentially unsafe operations may be accepted if the alternative is to
612
+ generate false positive errors for idiomatic code.
613
+
614
+ Some operations are allowed due to the TypedDict being
615
+ :term: `typing:assignable ` to ``Mapping[str, VT] `` or ``dict[str, VT] ``.
616
+ The two following sections will expand on that.
617
+
618
+ Interaction with Mapping[str, VT]
619
+ ---------------------------------
590
620
591
621
A TypedDict type is :term: `typing:assignable ` to a type of the form ``Mapping[str, VT] ``
592
622
when all value types of the items in the TypedDict
@@ -618,12 +648,12 @@ and ``items()`` on such TypedDict types::
618
648
reveal_type(movie.items()) # Revealed type is 'dict_items[str, str]'
619
649
reveal_type(movie.values()) # Revealed type is 'dict_values[str, str]'
620
650
621
- Interaction with dict[KT , VT]
622
- -----------------------------
651
+ Interaction with dict[str , VT]
652
+ ------------------------------
623
653
624
654
Because the presence of ``extra_items `` on a closed TypedDict type
625
655
prohibits additional required keys in its :term: `typing:structural `
626
- :term: `typing: subtypes <subtype> `, we can determine if the TypedDict type and
656
+ :term: `subtypes <subtype> `, we can determine if the TypedDict type and
627
657
its structural subtypes will ever have any required key during static analysis.
628
658
629
659
The TypedDict type is :term: `typing:assignable ` to ``dict[str, VT] `` if all
@@ -708,8 +738,25 @@ been removed in Python 3.13.
708
738
Because this is a type-checking feature, it can be made available to older
709
739
versions as long as the type checker supports it.
710
740
711
- Open Issues
712
- ===========
741
+ Rejected Ideas
742
+ ==============
743
+
744
+ Use ``@final `` instead of ``closed `` Class Parameter
745
+ -----------------------------------------------------
746
+
747
+ This was discussed `here <https://github.com/python/mypy/issues/7981 >`__.
748
+
749
+ Quoting a relevant `comment
750
+ <https://github.com/python/mypy/issues/7981#issuecomment-2080161813> `__
751
+ from Eric Traut:
752
+
753
+ The @final class decorator indicates that a class cannot be subclassed. This
754
+ makes sense for classes that define nominal types. However, TypedDict is a
755
+ structural type, similar to a Protocol. That means two TypedDict classes
756
+ with different names but the same field definitions are equivalent types.
757
+ Their names and hierarchies don't matter for determining type consistency.
758
+ For that reason, @final has no impact on a TypedDict type consistency rules,
759
+ nor should it change the behavior of items or values.
713
760
714
761
Use a Special ``__extra_items__ `` Key with the ``closed `` Class Parameter
715
762
-------------------------------------------------------------------------
@@ -725,7 +772,7 @@ where ``closed=True`` is required for ``__extra_items__`` to be treated
725
772
specially, to avoid key collision.
726
773
727
774
Some members of the community concern about the elegance of the syntax.
728
- Practiaclly , the key collision with a regular key can be mitigated with
775
+ Practically , the key collision with a regular key can be mitigated with
729
776
workarounds, but since using a reserved key is central to this proposal,
730
777
there are limited ways forward to address the concerns.
731
778
@@ -767,9 +814,6 @@ types altogether, but there are some disadvantages. `For example
767
814
- The types don't appear in an annotation context, so their evaluation will
768
815
not be deferred.
769
816
770
- Rejected Ideas
771
- ==============
772
-
773
817
Allowing Extra Items without Specifying the Type
774
818
------------------------------------------------
775
819
@@ -827,19 +871,24 @@ For example:
827
871
[index : string ]: number | string
828
872
}
829
873
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 ``.
874
+ While this restriction allows for sound indexed accesses with arbitrary keys,
875
+ it comes with usability limitations discussed in `TypeScript's issue tracker
876
+ <https://github.com/microsoft/TypeScript/issues/17867> `__.
877
+ A suggestion was to allow excluding the defined keys from the index signature,
878
+ to define a type like ``MovieWithExtraNumber ``. This probably involves
879
+ subtraction types, which is beyond the scope of this PEP.
835
880
836
881
Reference Implementation
837
882
========================
838
883
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
884
+ This is supported in `pyright 1.1.386
885
+ <https://github.com/microsoft/pyright/releases/tag/1.1.386> `_, and an earlier
886
+ revision is supported in `pyanalyze
841
887
0.12.0 <https://github.com/quora/pyanalyze/releases/tag/v0.12.0> `_.
842
888
889
+ This is also supported in `typing-extensions 4.13.0
890
+ <https://pypi.org/project/typing-extensions/4.13.0/> `_.
891
+
843
892
Acknowledgments
844
893
===============
845
894
0 commit comments