@@ -2,11 +2,13 @@ PEP: 764
22Title: Inlined typed dictionaries
33Author: Victorien Plot <
[email protected] >
44Sponsor: Eric Traut <erictr at microsoft.com>
5+ Discussions-To: https://discuss.python.org/t/78779
56Status: Draft
67Type: Standards Track
78Topic: Typing
89Created: 25-Oct-2024
910Python-Version: 3.14
11+ Post-History: `29-Jan-2025 <https://discuss.python.org/t/78779 >`__
1012
1113
1214Abstract
@@ -72,9 +74,6 @@ The new inlined syntax can be used to resolve these problems::
7274 def get_movie() -> TypedDict[{'name': str, 'year': int, 'production': TypedDict[{'name': str, 'location': str}]}]:
7375 ...
7476
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-
7877While less useful (as the functional or even the class-based syntax can be
7978used), inlined typed dictionaries can be assigned to a variable, as an alias::
8079
@@ -87,8 +86,8 @@ used), inlined typed dictionaries can be assigned to a variable, as an alias::
8786Specification
8887=============
8988
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
9291semantics as the :ref: `functional syntax <typing:typeddict-functional-syntax >`
9392(the dictionary keys are strings representing the field names, and values are
9493valid :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
9897assigned a :class: `dict ` instance).
9998
10099Inlined 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 >`_
102101section).
103102
104103It is possible to define a nested inlined dictionary::
@@ -109,7 +108,7 @@ It is possible to define a nested inlined dictionary::
109108 Movie = TypedDict[{'name': str, 'production': {'location': str}}]
110109
111110Although 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::
113112
114113 Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}]
115114
@@ -135,13 +134,18 @@ are bound to some outer scope::
135134
136135 T = TypeVar('T')
137136
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+
139143
140144Typing specification changes
141145----------------------------
142146
143147The 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
145149:external+typing:token: `~expression-grammar:type_expression ` production will
146150be updated to include the inlined syntax:
147151
@@ -186,8 +190,20 @@ How to Teach This
186190The new inlined syntax will be documented both in the :mod: `typing ` module
187191documentation and the :ref: `typing specification <typing:typed-dictionaries >`.
188192
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+ ...
191207
192208
193209Reference Implementation
@@ -223,11 +239,11 @@ various reasons (expensive to process, evaluating them is not standardized).
223239
224240This would also require a name which is sometimes not relevant.
225241
226- Using ``dict `` with a single type argument
227- ------------------------------------------
242+ Using ``dict `` or `` typing.Dict `` with a single type argument
243+ -------------------------------------------------------------
228244
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::
231247
232248 def get_movie() -> dict[{'title': str}]: ...
233249
@@ -243,6 +259,10 @@ While this would avoid having to import :class:`~typing.TypedDict` from
243259* If future work extends what inlined typed dictionaries can do, we don't have
244260 to worry about impact of sharing the symbol with :class: `dict `.
245261
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+
246266Using a simple dictionary
247267-------------------------
248268
@@ -262,45 +282,28 @@ cases incompatible, especially for runtime introspection::
262282 # Raises a type error at runtime:
263283 def fn() -> {'a': int} | int: ...
264284
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+ ----------------------------------
276287
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::
283290
284291 InlinedBase = TypedDict[{'a': int}]
285292
286293 Inlined = TypedDict[InlinedBase, {'b': int}]
294+ # or, by providing a slice:
295+ Inlined = TypedDict[{'b': int} : (InlinedBase,)]
287296
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.
290300
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.
293303
294- def get_movie() -> Dict[{'title': str}]: ...
295304
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+ ===========
304307
305308Should inlined typed dictionaries be proper classes?
306309----------------------------------------------------
@@ -319,12 +322,22 @@ implementation to provide the introspection attributes (such as
319322:attr: `~typing.TypedDict.__total__ `), and tools relying on runtime
320323introspection would have to add proper support for this new type.
321324
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+
322334Inlined typed dictionaries and extra items
323335------------------------------------------
324336
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.
328341
329342
330343Copyright
0 commit comments