Skip to content

Commit c16b1df

Browse files
spec: Rewrite TypedDict spec (#2072)
* 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. * Apply suggestions from code review Co-authored-by: Joren Hammudoglu <[email protected]> * feedback * Carl feedback * more clarity --------- Co-authored-by: Joren Hammudoglu <[email protected]>
1 parent 0fc5f85 commit c16b1df

File tree

4 files changed

+693
-1167
lines changed

4 files changed

+693
-1167
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/concepts.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ attributes and/or methods.
6969

7070
If an object ``v`` is a member of the set of objects denoted by a fully static
7171
type ``T``, we can say that ``v`` is a "member of" the type ``T``, or ``v``
72-
"inhabits" ``T``.
72+
":term:`inhabits <inhabit>`" ``T``.
7373

7474
Gradual types
7575
~~~~~~~~~~~~~
@@ -298,9 +298,9 @@ visualize this analogy in the following table:
298298
* - ``B`` is :term:`equivalent` to ``A``
299299
- ``B`` is :term:`consistent` with ``A``
300300

301-
We can also define an **equivalence** relation on gradual types: the gradual
302-
types ``A`` and ``B`` are equivalent (that is, the same gradual type, not
303-
merely consistent with one another) if and only if all materializations of
301+
We can also define an **equivalence** relation on gradual types: the gradual
302+
types ``A`` and ``B`` are equivalent (that is, the same gradual type, not
303+
merely consistent with one another) if and only if all materializations of
304304
``A`` are also materializations of ``B``, and all materializations of ``B``
305305
are also materializations of ``A``.
306306

docs/spec/glossary.rst

Lines changed: 54 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`, or equivalently by setting ``extra_items=Never``.
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 key-value pairs beyond those specified in the TypedDict definition, but those
65+
values must be of the type specified by the ``extra_items=`` argument to the 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
@@ -79,11 +94,21 @@ This section defines a few terms that may be used elsewhere in the specification
7994
They can be :term:`materialized <materialize>` to a more static, or fully static,
8095
type. See :ref:`type-system-concepts`.
8196

97+
inhabit
98+
A value is said to inhabit a type if it is a member of the set of values
99+
represented by that type. For example, the value ``42`` inhabits the type
100+
``int``, and the value ``"hello"`` inhabits the type ``str``.
101+
82102
inline
83103
Inline type annotations are annotations that are included in the
84104
runtime code using :pep:`526` and
85105
:pep:`3107` syntax (the filename ends in ``.py``).
86106

107+
item
108+
In the context of a :ref:`TypedDict <typeddict>`, an item consists of a name
109+
(the dictionary key) and a type (representing the type that values corresponding to the key must have).
110+
Items may be :term:`required` or :term:`non-required`, and may be :term:`read-only` or writable.
111+
87112
materialize
88113
A :term:`gradual type` can be materialized to a more static type
89114
(possibly a :term:`fully static type`) by replacing :ref:`Any` with any
@@ -110,12 +135,41 @@ This section defines a few terms that may be used elsewhere in the specification
110135
``__class__`` is that type, or any of its subclasses, transitively. In
111136
contrast, see :term:`structural` types.
112137

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

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

0 commit comments

Comments
 (0)