1
1
PEP: 764
2
- Title: Inlined typed dictionaries
2
+ Title: Inline typed dictionaries
3
3
Author: Victorien Plot <contact@vctrn.dev>
4
4
Sponsor: Eric Traut <erictr at microsoft.com>
5
5
Discussions-To: https://discuss.python.org/t/78779
@@ -20,7 +20,7 @@ typed dictionaries. In both scenarios, it requires defining a class or
20
20
assigning to a value. In some situations, this can add unnecessary
21
21
boilerplate, especially if the typed dictionary is only used once.
22
22
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
24
24
:class: `~typing.TypedDict ` type::
25
25
26
26
from typing import TypedDict
@@ -69,17 +69,17 @@ Taking a simple function returning some nested structured data as an example::
69
69
Rationale
70
70
=========
71
71
72
- The new inlined syntax can be used to resolve these problems::
72
+ The new inline syntax can be used to resolve these problems::
73
73
74
74
def get_movie() -> TypedDict[{'name': str, 'year': int, 'production': TypedDict[{'name': str, 'location': str}]}]:
75
75
...
76
76
77
77
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::
79
79
80
- InlinedTD = TypedDict[{'name': str}]
80
+ InlineTD = TypedDict[{'name': str}]
81
81
82
- def get_movie() -> InlinedTD :
82
+ def get_movie() -> InlineTD :
83
83
...
84
84
85
85
@@ -96,11 +96,11 @@ comma-separated list of ``key: value`` pairs within braces constructor
96
96
argument (i.e. it is not allowed to use a variable which was previously
97
97
assigned a :class: `dict ` instance).
98
98
99
- Inlined typed dictionaries can be referred to as *anonymous *, meaning they
99
+ Inline typed dictionaries can be referred to as *anonymous *, meaning they
100
100
don't have a specific name (see the `runtime behavior <Runtime behavior >`_
101
101
section).
102
102
103
- It is possible to define a nested inlined dictionary::
103
+ It is possible to define a nested inline dictionary::
104
104
105
105
Movie = TypedDict[{'name': str, 'production': TypedDict[{'location': str}]}]
106
106
@@ -112,65 +112,75 @@ any :term:`typing:type qualifier` can be used for individual fields::
112
112
113
113
Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}]
114
114
115
- Inlined typed dictionaries are implicitly *total *, meaning all keys must be
115
+ Inline typed dictionaries are implicitly *total *, meaning all keys must be
116
116
present. Using the :data: `~typing.Required ` type qualifier is thus redundant.
117
117
118
- Type variables are allowed in inlined typed dictionaries, provided that they
118
+ Type variables are allowed in inline typed dictionaries, provided that they
119
119
are bound to some outer scope::
120
120
121
121
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`.
123
123
124
- reveal_type(C[int]().inlined_td ['name']) # Revealed type is 'int'
124
+ reveal_type(C[int]().inline_td ['name']) # Revealed type is 'int'
125
125
126
126
127
127
def fn[T](arg: T) -> TypedDict[{'name': T}]: ... # OK: `T` is scoped to the function `fn`.
128
128
129
129
reveal_type(fn('a')['name']) # Revealed type is 'str'
130
130
131
131
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.
133
133
134
134
135
135
T = TypeVar('T')
136
136
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.
138
138
139
139
140
140
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`.
142
142
143
143
144
+ Inline typed dictionaries can be extended::
145
+
146
+ InlineTD = TypedDict[{'a': int}]
147
+
148
+ class SubTD(InlineTD):
149
+ pass
150
+
144
151
Typing specification changes
145
152
----------------------------
146
153
147
- The inlined typed dictionary adds a new kind of
154
+ The inline typed dictionary adds a new kind of
148
155
:term: `typing:type expression `. As such, the
149
156
: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:
151
158
152
- .. productionlist :: inlined -typed-dictionaries-grammar
159
+ .. productionlist :: inline -typed-dictionaries-grammar
153
160
new-type_expression: `~expression-grammar:type_expression `
154
161
: | <TypedDict> '[' '{' (string: ':' `~expression-grammar:annotation_expression ` ',')* '}' ']'
155
162
: (where string is any string literal)
156
163
157
164
Runtime behavior
158
165
----------------
159
166
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
165
168
``T2 `` are of the same type::
166
169
167
170
from typing import TypedDict
168
171
169
172
T1 = TypedDict('T1', {'a': int})
170
173
T2 = TypedDict[{'a': int}]
171
174
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
+
174
184
175
185
Backwards Compatibility
176
186
=======================
@@ -187,12 +197,12 @@ There are no known security consequences arising from this PEP.
187
197
How to Teach This
188
198
=================
189
199
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
191
201
documentation and the :ref: `typing specification <typing:typed-dictionaries >`.
192
202
193
203
When complex dictionary structures are used, having everything defined on a
194
204
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::
196
206
197
207
def edit_movie(
198
208
movie: TypedDict[{
@@ -214,14 +224,18 @@ Mypy supports a similar syntax as an :option:`experimental feature <mypy:mypy.--
214
224
def test_values() -> {"int": int, "str": str}:
215
225
return {"int": 42, "str": "test"}
216
226
227
+ Support for this PEP is added in `this pull request <https://github.com/python/mypy/pull/18889 >`__.
228
+
217
229
Pyright added support for the new syntax in version `1.1.387 `_.
218
230
219
231
.. _1.1.387 : https://github.com/microsoft/pyright/releases/tag/1.1.387
220
232
221
233
Runtime implementation
222
234
----------------------
223
235
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 >`__.
225
239
226
240
227
241
Rejected Ideas
@@ -256,7 +270,7 @@ While this would avoid having to import :class:`~typing.TypedDict` from
256
270
parametrization overloads. On the other hand, :class: `~typing.TypedDict ` is
257
271
already a :term: `special form <typing:special form> `.
258
272
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
260
274
to worry about impact of sharing the symbol with :class: `dict `.
261
275
262
276
* :class: `typing.Dict ` has been deprecated (although not planned for removal)
@@ -288,13 +302,13 @@ Extending other typed dictionaries
288
302
Several syntaxes could be used to have the ability to extend other typed
289
303
dictionaries::
290
304
291
- InlinedBase = TypedDict[{'a': int}]
305
+ InlineBase = TypedDict[{'a': int}]
292
306
293
- Inlined = TypedDict[InlinedBase , {'b': int}]
307
+ Inline = TypedDict[InlineBase , {'b': int}]
294
308
# or, by providing a slice:
295
- Inlined = TypedDict[{'b': int} : (InlinedBase ,)]
309
+ Inline = TypedDict[{'b': int} : (InlineBase ,)]
296
310
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
298
312
existing syntax, adding this extension mechanism isn't compelling
299
313
enough to be supported, considering the added complexity.
300
314
@@ -305,37 +319,11 @@ use case.
305
319
Open Issues
306
320
===========
307
321
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
+ -----------------------------------------
336
324
337
325
: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
339
327
will be *closed * by default. This means :pep: `728 ` needs to be addressed
340
328
first, so that this PEP can be updated accordingly.
341
329
0 commit comments