11PEP: 764
2- Title: Inlined typed dictionaries
2+ Title: Inline typed dictionaries
33Author: Victorien Plot <
[email protected] >
44Sponsor: Eric Traut <erictr at microsoft.com>
55Discussions-To: https://discuss.python.org/t/78779
@@ -20,7 +20,7 @@ typed dictionaries. In both scenarios, it requires defining a class or
2020assigning to a value. In some situations, this can add unnecessary
2121boilerplate, 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::
6969Rationale
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
7777While 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
9696argument (i.e. it is not allowed to use a variable which was previously
9797assigned 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
100100don't have a specific name (see the `runtime behavior <Runtime behavior >`_
101101section).
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
116116present. 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
119119are 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+
144151Typing 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
157164Runtime 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
175185Backwards Compatibility
176186=======================
@@ -187,12 +197,12 @@ There are no known security consequences arising from this PEP.
187197How 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
191201documentation and the :ref: `typing specification <typing:typed-dictionaries >`.
192202
193203When complex dictionary structures are used, having everything defined on a
194204single 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+
217229Pyright 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
221233Runtime 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
227241Rejected 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
288302Several syntaxes could be used to have the ability to extend other typed
289303dictionaries::
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
298312existing syntax, adding this extension mechanism isn't compelling
299313enough to be supported, considering the added complexity.
300314
@@ -305,37 +319,11 @@ use case.
305319Open 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
339327will be *closed * by default. This means :pep: `728 ` needs to be addressed
340328first, so that this PEP can be updated accordingly.
341329
0 commit comments