@@ -2,11 +2,13 @@ PEP: 764
2
2
Title: Inlined typed dictionaries
3
3
Author: Victorien Plot <
[email protected] >
4
4
Sponsor: Eric Traut <erictr at microsoft.com>
5
+ Discussions-To: https://discuss.python.org/t/78779
5
6
Status: Draft
6
7
Type: Standards Track
7
8
Topic: Typing
8
9
Created: 25-Oct-2024
9
10
Python-Version: 3.14
11
+ Post-History: `29-Jan-2025 <https://discuss.python.org/t/78779 >`__
10
12
11
13
12
14
Abstract
@@ -72,9 +74,6 @@ The new inlined syntax can be used to resolve these problems::
72
74
def get_movie() -> TypedDict[{'name': str, 'year': int, 'production': TypedDict[{'name': str, 'location': str}]}]:
73
75
...
74
76
75
- It is recommended to *only * make use of inlined typed dictionaries when the
76
- structured data isn't too large, as this can quickly become hard to read.
77
-
78
77
While less useful (as the functional or even the class-based syntax can be
79
78
used), inlined typed dictionaries can be assigned to a variable, as an alias::
80
79
@@ -87,8 +86,8 @@ used), inlined typed dictionaries can be assigned to a variable, as an alias::
87
86
Specification
88
87
=============
89
88
90
- The :class: `~typing.TypedDict ` class is made subscriptable, and accepts a
91
- single type argument which must be a :class: `dict `, following the same
89
+ The :class: `~typing.TypedDict ` special form is made subscriptable, and accepts
90
+ a single type argument which must be a :class: `dict `, following the same
92
91
semantics as the :ref: `functional syntax <typing:typeddict-functional-syntax >`
93
92
(the dictionary keys are strings representing the field names, and values are
94
93
valid :ref: `annotation expressions <typing:annotation-expression >`). Only the
@@ -98,7 +97,7 @@ argument (i.e. it is not allowed to use a variable which was previously
98
97
assigned a :class: `dict ` instance).
99
98
100
99
Inlined typed dictionaries can be referred to as *anonymous *, meaning they
101
- don't have a name (see the `runtime behavior <Runtime behavior >`_
100
+ don't have a specific name (see the `runtime behavior <Runtime behavior >`_
102
101
section).
103
102
104
103
It is possible to define a nested inlined dictionary::
@@ -109,7 +108,7 @@ It is possible to define a nested inlined dictionary::
109
108
Movie = TypedDict[{'name': str, 'production': {'location': str}}]
110
109
111
110
Although it is not possible to specify any class arguments such as ``total ``,
112
- any :external+typing: term: `type qualifier ` can be used for individual fields::
111
+ any :term: `typing: type qualifier ` can be used for individual fields::
113
112
114
113
Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}]
115
114
@@ -135,13 +134,18 @@ are bound to some outer scope::
135
134
136
135
T = TypeVar('T')
137
136
138
- InlinedTD = TypedDict[{'name': T}] # Not OK, `T` refers to a type variable that is not bound to any scope.
137
+ InlinedTD = TypedDict[{'name': T}] # OK, same as the previous type alias, but using the old-style syntax.
138
+
139
+
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`.
142
+
139
143
140
144
Typing specification changes
141
145
----------------------------
142
146
143
147
The inlined typed dictionary adds a new kind of
144
- :external+typing: term: `type expression `. As such, the
148
+ :term: `typing: type expression `. As such, the
145
149
:external+typing:token: `~expression-grammar:type_expression ` production will
146
150
be updated to include the inlined syntax:
147
151
@@ -186,8 +190,20 @@ How to Teach This
186
190
The new inlined syntax will be documented both in the :mod: `typing ` module
187
191
documentation and the :ref: `typing specification <typing:typed-dictionaries >`.
188
192
189
- As mentioned in the `Rationale `_, it should be mentioned that inlined typed
190
- dictionaries should be used for small structured data to not hurt readability.
193
+ When complex dictionary structures are used, having everything defined on a
194
+ single line can hurt readability. Code formatters can help by formatting the
195
+ inlined typed dictionary across multiple lines::
196
+
197
+ def edit_movie(
198
+ movie: TypedDict[{
199
+ 'name': str,
200
+ 'year': int,
201
+ 'production': TypedDict[{
202
+ 'location': str,
203
+ }],
204
+ }],
205
+ ) -> None:
206
+ ...
191
207
192
208
193
209
Reference Implementation
@@ -223,11 +239,11 @@ various reasons (expensive to process, evaluating them is not standardized).
223
239
224
240
This would also require a name which is sometimes not relevant.
225
241
226
- Using ``dict `` with a single type argument
227
- ------------------------------------------
242
+ Using ``dict `` or `` typing.Dict `` with a single type argument
243
+ -------------------------------------------------------------
228
244
229
- We could reuse :class: `dict ` with a single type argument to express the same
230
- concept::
245
+ We could reuse :class: `dict ` or :class: ` typing.Dict ` with a single type
246
+ argument to express the same concept::
231
247
232
248
def get_movie() -> dict[{'title': str}]: ...
233
249
@@ -243,6 +259,10 @@ While this would avoid having to import :class:`~typing.TypedDict` from
243
259
* If future work extends what inlined typed dictionaries can do, we don't have
244
260
to worry about impact of sharing the symbol with :class: `dict `.
245
261
262
+ * :class: `typing.Dict ` has been deprecated (although not planned for removal)
263
+ by :pep: `585 `. Having it used for a new typing feature would be confusing
264
+ for users (and would require changes in code linters).
265
+
246
266
Using a simple dictionary
247
267
-------------------------
248
268
@@ -262,45 +282,28 @@ cases incompatible, especially for runtime introspection::
262
282
# Raises a type error at runtime:
263
283
def fn() -> {'a': int} | int: ...
264
284
265
- Open Issues
266
- ===========
267
-
268
- Subclassing an inlined typed dictionary
269
- ---------------------------------------
270
-
271
- Should we allow the following?::
272
-
273
- from typing import TypedDict
274
-
275
- InlinedTD = TypedDict[{'a': int}]
285
+ Extending other typed dictionaries
286
+ ----------------------------------
276
287
277
-
278
- class SubTD(InlinedTD):
279
- pass
280
-
281
- What about defining an inlined typed dictionay extending another typed
282
- dictionary?::
288
+ Several syntaxes could be used to have the ability to extend other typed
289
+ dictionaries::
283
290
284
291
InlinedBase = TypedDict[{'a': int}]
285
292
286
293
Inlined = TypedDict[InlinedBase, {'b': int}]
294
+ # or, by providing a slice:
295
+ Inlined = TypedDict[{'b': int} : (InlinedBase,)]
287
296
288
- Using ``typing.Dict `` with a single argument
289
- --------------------------------------------
297
+ As inlined typed dictionaries are meant to only support a subset of the
298
+ existing syntax, adding this extension mechanism isn't compelling
299
+ enough to be supported, considering the added complexity.
290
300
291
- While using :class: ` dict ` isn't ideal, we could make use of
292
- :class: ` typing.Dict ` with a single argument::
301
+ If intersections were to be added into the type system, it could cover this
302
+ use case.
293
303
294
- def get_movie() -> Dict[{'title': str}]: ...
295
304
296
- It is less verbose, doesn't have the baggage of :class: `dict `, and is
297
- already defined as some kind of special form.
298
-
299
- However, it is currently marked as deprecated (although not scheduled for
300
- removal), so it might be confusing to undeprecate it.
301
-
302
- This would also set a precedent on typing constructs being parametrizable
303
- with a different number of type arguments.
305
+ Open Issues
306
+ ===========
304
307
305
308
Should inlined typed dictionaries be proper classes?
306
309
----------------------------------------------------
@@ -319,12 +322,22 @@ implementation to provide the introspection attributes (such as
319
322
:attr: `~typing.TypedDict.__total__ `), and tools relying on runtime
320
323
introspection would have to add proper support for this new type.
321
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
+
322
334
Inlined typed dictionaries and extra items
323
335
------------------------------------------
324
336
325
- :pep: `728 ` introduces the concept of *closed * type dictionaries. Inlined
326
- typed dictionaries should probably be implicitly *closed *, but it may be
327
- better to wait for :pep: `728 ` to be accepted first.
337
+ :pep: `728 ` introduces the concept of :ref: `closed <typed-dict-closed >` type
338
+ dictionaries. If this PEP were to be accepted, inlined typed dictionaries
339
+ will be *closed * by default. This means :pep: `728 ` needs to be addressed
340
+ first, so that this PEP can be updated accordingly.
328
341
329
342
330
343
Copyright
0 commit comments