Skip to content

Commit 25932fe

Browse files
committed
spec: Rewrite TypedDict spec
This is an edit of the TypedDict spec for clarity and flow. My goal was to unify the pieces of the spec that derive from the various PEPs into a coherent whole. I removed excessive examples and motivations: the spec should specify, not justify. The length of the spec chapter is reduced by more than half. This change is on top of #2068 (adding PEP 728). The general approach I took is to first define the kinds of TypedDicts that can exist, then explain the syntax for defining TypedDicts, then discuss other aspects of TypedDict types. I introduce some new terminology around PEP 728 to make it easier to talk about the different kinds of TypedDict. TypedDicts are defined to have a property called openness, which can have three states: - Open: all TypedDicts prior to PEP 728 - Closed: no extra keys are allowed (closed=True) - With extra items: extra_items=... from PEP 728 I retained existing text where it made sense but also wrote some from scratch.
1 parent b57e1ff commit 25932fe

File tree

3 files changed

+669
-1161
lines changed

3 files changed

+669
-1161
lines changed

docs/spec/callables.rst

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,24 +180,61 @@ generated. For example::
180180
# so **kwargs can contain
181181
# a "name" keyword.
182182

183-
Required and non-required keys
184-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
185-
186-
By default all keys in a ``TypedDict`` are required. This behavior can be
187-
overridden by setting the dictionary's ``total`` parameter as ``False``.
188-
Moreover, :pep:`655` introduced new type qualifiers - ``typing.Required`` and
189-
``typing.NotRequired`` - that enable specifying whether a particular key is
190-
required or not::
191-
192-
class Movie(TypedDict):
193-
title: str
194-
year: NotRequired[int]
183+
Required and non-required items
184+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
195185

186+
Items in a TypedDict may be either :term:`required` or :term:`non-required`.
196187
When using a ``TypedDict`` to type ``**kwargs`` all of the required and
197188
non-required keys should correspond to required and non-required function
198-
keyword parameters. Therefore, if a required key is not supported by the
189+
keyword parameters. Therefore, if a required key is not provided by the
199190
caller, then an error must be reported by type checkers.
200191

192+
Read-only items
193+
^^^^^^^^^^^^^^^
194+
195+
TypedDict items may also be :term:`read-only`. Marking one or more of the items of a TypedDict
196+
used to type ``**kwargs`` as read-only will have no effect on the type signature of the method.
197+
However, it *will* prevent the item from being modified in the body of the function::
198+
199+
class Args(TypedDict):
200+
key1: int
201+
key2: str
202+
203+
class ReadOnlyArgs(TypedDict):
204+
key1: ReadOnly[int]
205+
key2: ReadOnly[str]
206+
207+
class Function(Protocol):
208+
def __call__(self, **kwargs: Unpack[Args]) -> None: ...
209+
210+
def impl(**kwargs: Unpack[ReadOnlyArgs]) -> None:
211+
kwargs["key1"] = 3 # Type check error: key1 is readonly
212+
213+
fn: Function = impl # Accepted by type checker: function signatures are identical
214+
215+
Extra items
216+
^^^^^^^^^^^
217+
218+
If the TypedDict used for annotating ``**kwargs`` is defined to allow
219+
:term:`extra items`, arbitrary additional keyword arguments of the right
220+
type may be passed to the function::
221+
222+
class MovieNoExtra(TypedDict):
223+
name: str
224+
225+
class MovieExtra(TypedDict, extra_items=int):
226+
name: str
227+
228+
def f(**kwargs: Unpack[MovieNoExtra]) -> None: ...
229+
def g(**kwargs: Unpack[MovieExtra]) -> None: ...
230+
231+
# Should be equivalent to:
232+
def f(*, name: str) -> None: ...
233+
def g(*, name: str, **kwargs: int) -> None: ...
234+
235+
f(name="No Country for Old Men", year=2007) # Not OK. Unrecognized item
236+
g(name="No Country for Old Men", year=2007) # OK
237+
201238
Assignment
202239
^^^^^^^^^^
203240

docs/spec/glossary.rst

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ This section defines a few terms that may be used elsewhere in the specification
2727
``B``, respectively, such that ``B'`` is a subtype of ``A'``. See
2828
:ref:`type-system-concepts`.
2929

30+
closed
31+
A :ref:`TypedDict <typeddict>` type is closed if it may not contain any
32+
additional :term:`items <item>` beyond those specified in the TypedDict definition.
33+
A closed TypedDict can be created using the ``closed=True`` argument to
34+
:py:func:`typing.TypedDict`.
35+
Compare :term:`extra items` and :term:`open`.
36+
3037
consistent
3138
Two :term:`fully static types <fully static type>` are "consistent with"
3239
each other if they are :term:`equivalent`. Two gradual types are
@@ -52,6 +59,14 @@ This section defines a few terms that may be used elsewhere in the specification
5259
also materializations of ``B``, and all materializations of ``B`` are
5360
also materializations of ``A``.
5461

62+
extra items
63+
A :ref:`TypedDict <typeddict>` type with extra items may contain arbitrary
64+
additional :term:`items <item>` beyond those specified in the TypedDict definition, but those
65+
items must be of the type specified by the TypedDict definition.
66+
A TypedDict with extra items can be created using the ``extra_items=``
67+
argument to :py:func:`typing.TypedDict`. Extra items may or may not be
68+
:term:`read-only`. Compare :term:`closed` and :term:`open`.
69+
5570
fully static type
5671
A type is "fully static" if it does not contain any :term:`gradual form`.
5772
A fully static type represents a set of possible runtime values. Fully
@@ -84,6 +99,12 @@ This section defines a few terms that may be used elsewhere in the specification
8499
runtime code using :pep:`526` and
85100
:pep:`3107` syntax (the filename ends in ``.py``).
86101

102+
item
103+
In the context of a :ref:`TypedDict <typeddict>`, an item is a key/value
104+
pair defined in the TypedDict definition. Each item has a name (the key)
105+
and a type (the value). Items may be :term:`required` or
106+
:term:`non-required`, and may be :term:`read-only` or writable.
107+
87108
materialize
88109
A :term:`gradual type` can be materialized to a more static type
89110
(possibly a :term:`fully static type`) by replacing :ref:`Any` with any
@@ -110,12 +131,41 @@ This section defines a few terms that may be used elsewhere in the specification
110131
``__class__`` is that type, or any of its subclasses, transitively. In
111132
contrast, see :term:`structural` types.
112133

134+
non-required
135+
If an :term:`item` in a :ref:`TypedDict <typeddict>` is non-required, it may or
136+
may not be present on an object of that TypedDict type, but if it is present
137+
it must be of the type specified by the TypedDict definition.
138+
Items can be marked as non-required using the :py:data:`typing.NotRequired` qualifier
139+
or the ``total=False`` argument to :py:func:`typing.TypedDict`. Compare :term:`required`.
140+
141+
open
142+
A :ref:`TypedDict <typeddict>` type is open if it may contain arbitrary
143+
additional :term:`items <item>` beyond those specified in the TypedDict definition.
144+
This is the default behavior for TypedDicts that do not use the ``closed=True``
145+
or ``extra_items=`` arguments to :py:func:`typing.TypedDict`.
146+
Open TypedDicts behave similarly to TypedDicts with :term:`extra items` of type
147+
``ReadOnly[object]``, but differ in some behaviors; see the TypedDict specification
148+
chapter for details.
149+
Compare :term:`extra items` and :term:`closed`.
150+
113151
package
114152
A directory or directories that namespace Python modules.
115153
(Note the distinction between packages and :term:`distributions <distribution>`.
116154
While most distributions are named after the one package they install, some
117155
distributions install multiple packages.)
118156

157+
read-only
158+
A read-only :term:`item` in a :ref:`TypedDict <typeddict>` may not be modified.
159+
Attempts to assign to or delete that item
160+
should be reported as type errors by a type checker. Read-only items are created
161+
using the :py:data:`typing.ReadOnly` qualifier.
162+
163+
required
164+
If an :term:`item` in a :ref:`TypedDict <typeddict>` is required, it must be present
165+
in any object of that TypedDict type. Items are
166+
required by default, but items can also be explicitly marked as required using
167+
the :py:data:`typing.Required` qualifier. Compare :term:`non-required`.
168+
119169
special form
120170
A special form is an object that has a special meaning within the type system,
121171
comparable to a keyword in the language grammar. Examples include ``Any``,

0 commit comments

Comments
 (0)