Skip to content

Commit 0791060

Browse files
ViicosAA-Turner
andauthored
PEP 764: Second round of updates (#4386)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
1 parent c23cc43 commit 0791060

File tree

1 file changed

+50
-62
lines changed

1 file changed

+50
-62
lines changed

peps/pep-0764.rst

+50-62
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PEP: 764
2-
Title: Inlined typed dictionaries
2+
Title: Inline typed dictionaries
33
Author: Victorien Plot <contact@vctrn.dev>
44
Sponsor: Eric Traut <erictr at microsoft.com>
55
Discussions-To: https://discuss.python.org/t/78779
@@ -20,7 +20,7 @@ typed dictionaries. In both scenarios, it requires defining a class or
2020
assigning to a value. In some situations, this can add unnecessary
2121
boilerplate, especially if the typed dictionary is only used once.
2222

23-
This PEP proposes the addition of a new inlined syntax, by subscripting the
23+
This PEP proposes the addition of a new inline syntax, by subscripting the
2424
:class:`~typing.TypedDict` type::
2525

2626
from typing import TypedDict
@@ -69,17 +69,17 @@ Taking a simple function returning some nested structured data as an example::
6969
Rationale
7070
=========
7171

72-
The new inlined syntax can be used to resolve these problems::
72+
The new inline syntax can be used to resolve these problems::
7373

7474
def get_movie() -> TypedDict[{'name': str, 'year': int, 'production': TypedDict[{'name': str, 'location': str}]}]:
7575
...
7676

7777
While less useful (as the functional or even the class-based syntax can be
78-
used), inlined typed dictionaries can be assigned to a variable, as an alias::
78+
used), inline typed dictionaries can be assigned to a variable, as an alias::
7979

80-
InlinedTD = TypedDict[{'name': str}]
80+
InlineTD = TypedDict[{'name': str}]
8181

82-
def get_movie() -> InlinedTD:
82+
def get_movie() -> InlineTD:
8383
...
8484

8585

@@ -96,11 +96,11 @@ comma-separated list of ``key: value`` pairs within braces constructor
9696
argument (i.e. it is not allowed to use a variable which was previously
9797
assigned a :class:`dict` instance).
9898

99-
Inlined typed dictionaries can be referred to as *anonymous*, meaning they
99+
Inline typed dictionaries can be referred to as *anonymous*, meaning they
100100
don't have a specific name (see the `runtime behavior <Runtime behavior>`_
101101
section).
102102

103-
It is possible to define a nested inlined dictionary::
103+
It is possible to define a nested inline dictionary::
104104

105105
Movie = TypedDict[{'name': str, 'production': TypedDict[{'location': str}]}]
106106

@@ -112,65 +112,75 @@ any :term:`typing:type qualifier` can be used for individual fields::
112112

113113
Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}]
114114

115-
Inlined typed dictionaries are implicitly *total*, meaning all keys must be
115+
Inline typed dictionaries are implicitly *total*, meaning all keys must be
116116
present. Using the :data:`~typing.Required` type qualifier is thus redundant.
117117

118-
Type variables are allowed in inlined typed dictionaries, provided that they
118+
Type variables are allowed in inline typed dictionaries, provided that they
119119
are bound to some outer scope::
120120

121121
class C[T]:
122-
inlined_td: TypedDict[{'name': T}] # OK, `T` is scoped to the class `C`.
122+
inline_td: TypedDict[{'name': T}] # OK, `T` is scoped to the class `C`.
123123

124-
reveal_type(C[int]().inlined_td['name']) # Revealed type is 'int'
124+
reveal_type(C[int]().inline_td['name']) # Revealed type is 'int'
125125

126126

127127
def fn[T](arg: T) -> TypedDict[{'name': T}]: ... # OK: `T` is scoped to the function `fn`.
128128

129129
reveal_type(fn('a')['name']) # Revealed type is 'str'
130130

131131

132-
type InlinedTD[T] = TypedDict[{'name': T}] # OK, `T` is scoped to the type alias.
132+
type InlineTD[T] = TypedDict[{'name': T}] # OK, `T` is scoped to the type alias.
133133

134134

135135
T = TypeVar('T')
136136

137-
InlinedTD = TypedDict[{'name': T}] # OK, same as the previous type alias, but using the old-style syntax.
137+
InlineTD = TypedDict[{'name': T}] # OK, same as the previous type alias, but using the old-style syntax.
138138

139139

140140
def func():
141-
InlinedTD = TypedDict[{'name': T}] # Not OK: `T` refers to a type variable that is not bound to the scope of `func`.
141+
InlineTD = TypedDict[{'name': T}] # Not OK: `T` refers to a type variable that is not bound to the scope of `func`.
142142

143143

144+
Inline typed dictionaries can be extended::
145+
146+
InlineTD = TypedDict[{'a': int}]
147+
148+
class SubTD(InlineTD):
149+
pass
150+
144151
Typing specification changes
145152
----------------------------
146153

147-
The inlined typed dictionary adds a new kind of
154+
The inline typed dictionary adds a new kind of
148155
:term:`typing:type expression`. As such, the
149156
:external+typing:token:`~expression-grammar:type_expression` production will
150-
be updated to include the inlined syntax:
157+
be updated to include the inline syntax:
151158

152-
.. productionlist:: inlined-typed-dictionaries-grammar
159+
.. productionlist:: inline-typed-dictionaries-grammar
153160
new-type_expression: `~expression-grammar:type_expression`
154161
: | <TypedDict> '[' '{' (string: ':' `~expression-grammar:annotation_expression` ',')* '}' ']'
155162
: (where string is any string literal)
156163

157164
Runtime behavior
158165
----------------
159166

160-
Although :class:`~typing.TypedDict` is commonly referred as a class, it is
161-
implemented as a function at runtime. To be made subscriptable, it will be
162-
changed to be a class.
163-
164-
Creating an inlined typed dictionary results in a new class, so ``T1`` and
167+
Creating an inline typed dictionary results in a new class, so ``T1`` and
165168
``T2`` are of the same type::
166169

167170
from typing import TypedDict
168171

169172
T1 = TypedDict('T1', {'a': int})
170173
T2 = TypedDict[{'a': int}]
171174

172-
As inlined typed dictionaries are are meant to be *anonymous*, their
173-
:attr:`~type.__name__` attribute will be set to an empty string.
175+
As inline typed dictionaries are meant to be *anonymous*, their
176+
:attr:`~type.__name__` attribute will be set to the ``<inline TypedDict>``
177+
string literal. In the future, an explicit class attribute could be added
178+
to make them distinguishable from named classes.
179+
180+
Although :class:`~typing.TypedDict` is documented as a class, the way it is
181+
defined is an implementation detail. The implementation will have to be tweaked
182+
so that :class:`~typing.TypedDict` can be made subscriptable.
183+
174184

175185
Backwards Compatibility
176186
=======================
@@ -187,12 +197,12 @@ There are no known security consequences arising from this PEP.
187197
How to Teach This
188198
=================
189199

190-
The new inlined syntax will be documented both in the :mod:`typing` module
200+
The new inline syntax will be documented both in the :mod:`typing` module
191201
documentation and the :ref:`typing specification <typing:typed-dictionaries>`.
192202

193203
When complex dictionary structures are used, having everything defined on a
194204
single line can hurt readability. Code formatters can help by formatting the
195-
inlined typed dictionary across multiple lines::
205+
inline type dictionary across multiple lines::
196206

197207
def edit_movie(
198208
movie: TypedDict[{
@@ -214,14 +224,18 @@ Mypy supports a similar syntax as an :option:`experimental feature <mypy:mypy.--
214224
def test_values() -> {"int": int, "str": str}:
215225
return {"int": 42, "str": "test"}
216226

227+
Support for this PEP is added in `this pull request <https://github.com/python/mypy/pull/18889>`__.
228+
217229
Pyright added support for the new syntax in version `1.1.387`_.
218230

219231
.. _1.1.387: https://github.com/microsoft/pyright/releases/tag/1.1.387
220232

221233
Runtime implementation
222234
----------------------
223235

224-
A draft implementation is available `here <https://github.com/Viicos/cpython/commit/49e5a83f>`_.
236+
The necessary changes were first implemented in
237+
`typing_extensions <https://typing-extensions.readthedocs.io/en/latest/>`_
238+
in `this pull request <https://github.com/python/typing_extensions/pull/580>`__.
225239

226240

227241
Rejected Ideas
@@ -256,7 +270,7 @@ While this would avoid having to import :class:`~typing.TypedDict` from
256270
parametrization overloads. On the other hand, :class:`~typing.TypedDict` is
257271
already a :term:`special form <typing:special form>`.
258272

259-
* If future work extends what inlined typed dictionaries can do, we don't have
273+
* If future work extends what inline typed dictionaries can do, we don't have
260274
to worry about impact of sharing the symbol with :class:`dict`.
261275

262276
* :class:`typing.Dict` has been deprecated (although not planned for removal)
@@ -288,13 +302,13 @@ Extending other typed dictionaries
288302
Several syntaxes could be used to have the ability to extend other typed
289303
dictionaries::
290304

291-
InlinedBase = TypedDict[{'a': int}]
305+
InlineBase = TypedDict[{'a': int}]
292306

293-
Inlined = TypedDict[InlinedBase, {'b': int}]
307+
Inline = TypedDict[InlineBase, {'b': int}]
294308
# or, by providing a slice:
295-
Inlined = TypedDict[{'b': int} : (InlinedBase,)]
309+
Inline = TypedDict[{'b': int} : (InlineBase,)]
296310

297-
As inlined typed dictionaries are meant to only support a subset of the
311+
As inline typed dictionaries are meant to only support a subset of the
298312
existing syntax, adding this extension mechanism isn't compelling
299313
enough to be supported, considering the added complexity.
300314

@@ -305,37 +319,11 @@ use case.
305319
Open Issues
306320
===========
307321

308-
Should inlined typed dictionaries be proper classes?
309-
----------------------------------------------------
310-
311-
The PEP currently defines inlined typed dictionaries as type objects, to be in
312-
line with the existing syntaxes. To work around the fact that they don't have
313-
a name, their :attr:`~type.__name__` attribute is set to an empty string.
314-
315-
This is somewhat arbitrary, and an alternative name could be used as well
316-
(e.g. ``'<TypedDict>'``).
317-
318-
Alternatively, inlined typed dictionaries could be defined as instances of a
319-
new (internal) typing class, e.g. :class:`!typing._InlinedTypedDict`. While
320-
this solves the naming issue, it requires extra logic in the runtime
321-
implementation to provide the introspection attributes (such as
322-
:attr:`~typing.TypedDict.__total__`), and tools relying on runtime
323-
introspection would have to add proper support for this new type.
324-
325-
Depending on the outcome of the runtime implementation, we can more or less
326-
easily allow extending inlined typed dictionaries::
327-
328-
InlinedTD = TypedDict[{'a': int}]
329-
330-
# If `InlinedTD` is a typing._InlinedTypedDict instance, this adds complexity:
331-
class SubTD(InlinedTD):
332-
pass
333-
334-
Inlined typed dictionaries and extra items
335-
------------------------------------------
322+
Inline typed dictionaries and extra items
323+
-----------------------------------------
336324

337325
:pep:`728` introduces the concept of :ref:`closed <typed-dict-closed>` type
338-
dictionaries. If this PEP were to be accepted, inlined typed dictionaries
326+
dictionaries. If this PEP were to be accepted, inline typed dictionaries
339327
will be *closed* by default. This means :pep:`728` needs to be addressed
340328
first, so that this PEP can be updated accordingly.
341329

0 commit comments

Comments
 (0)