@@ -5,7 +5,7 @@ Discussions-To:
55Status: Draft
66Type: Standards Track
77Topic: Typing
8- Created: 23 -Oct-2024
8+ Created: 25 -Oct-2024
99Post-History:
1010Python-Version: 3.14
1111Resolution:
@@ -73,16 +73,17 @@ Rationale
7373
7474The new inlined syntax can be used to resolve these problems::
7575
76- def get_movie() -> TypedDict[{'name': str, year: int, 'production': {'name': str, 'location': str}}]:
76+ def get_movie() -> TypedDict[{'name': str, ' year' : int, 'production': TypedDict[ {'name': str, 'location': str}] }]:
7777 ...
7878
7979It is recommended to *only * make use of inlined typed dictionaries when the
80- structured data isn't too large, as this can quickly get hard to read.
80+ structured data isn't too large, as this can quickly become hard to read.
8181
8282While less useful (as the functional or even the class-based syntax can be
83- used), inlined typed dictionaries can be defined as a type alias::
83+ used), inlined typed dictionaries can be assigned to a variable, as an alias::
84+
85+ InlinedTD = TypedDict[{'name': str}]
8486
85- type InlinedDict = TypedDict[{'name': str}]
8687
8788Specification
8889=============
@@ -97,7 +98,43 @@ Inlined typed dictionaries can be referred as being *anonymous*, meaning they
9798don't have a name. For this reason, their :attr: `~type.__name__ ` attribute
9899will be set to an empty string.
99100
100- It is not possible to specify any class arguments such as ``total ``.
101+ It is possible to define a nested inlined dictionary::
102+
103+ Movie = TypedDict[{'name': str, 'production': TypedDict[{'location': str}]}]
104+
105+ # Note that the following is invalid as per the updated `type_expression` production:
106+ Movie = TypedDict[{'name': str, 'production': {'location': str}}]
107+
108+ Although it is not possible to specify any class arguments such as ``total ``,
109+ Any :external+typing:term: `type qualifier ` can be used for individual fields::
110+
111+ Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}]
112+
113+ Inlined typed dictionaries are implicitly *total *, meaning all keys must be
114+ present. Using the :data: `~typing.Required ` type qualifier is thus redundant.
115+
116+ Type variables are allowed in inlined typed dictionaries, provided that they
117+ are bound to some outer scope::
118+
119+ class C[T]:
120+ inlined_td: TypedDict[{'name': T}] # OK, `T` is scoped to the class `C`.
121+
122+ reveal_type(C[int]().inlined_td['name']) # Revealed type is 'int'
123+
124+
125+ def fn[T](arg: T) -> TypedDict[{'name': T}]: ... # OK: `T` is scoped to the function `fn`.
126+
127+ reveal_type(fn('a')['name']) # Revealed type is 'str'
128+
129+
130+ type InlinedTD[T] = TypedDict[{'name': T}] # OK
131+
132+
133+ T = TypeVar('T')
134+
135+ InlinedTD = TypedDict[{'name': T}] # Not OK, `T` refers to a type variable that is not bound to any scope.
136+
137+ **TODO ** closed
101138
102139Runtime behavior
103140----------------
@@ -107,7 +144,7 @@ implemented as a function at runtime. To be made subscriptable, it will be
107144changed to be a class.
108145
109146Creating an inlined typed dictionary results in a new class, so both syntaxes
110- return the same type::
147+ return the same type (apart from the different :attr: ` ~type.__name__ `) ::
111148
112149 from typing import TypedDict
113150
@@ -117,12 +154,15 @@ return the same type::
117154Typing specification changes
118155----------------------------
119156
120- The inlined typed dictionary syntax adds a new valid location for
121- :term: `type expressions <typing:type expression> `. As such, the specification
122- on :ref: ` valid locations < typing:valid-types >` will need to be updated, most
123- likely by adding a new item to the list :
157+ The inlined typed dictionary adds a new kind of
158+ :term: `type expressions <typing:type expression> `. As such, the
159+ :external+ typing:token: ` ~expression-grammar:type_expression ` production will
160+ need to be updated to include the inlined syntax :
124161
125- * The definitions of the fields in the inlined typed dictionary syntax
162+ .. productionlist :: inlined-typed-dictionaries-grammar
163+ new-type_expression: `~expression-grammar:type_expression `
164+ : | <TypedDict> '[' '{' (string: ':' `~expression-grammar:annotation_expression ` ',')* '}' ']'
165+ (where string is any string literal)
126166
127167
128168Backwards Compatibility
@@ -156,11 +196,9 @@ Mypy supports a similar syntax as an :option:`experimental feature <mypy:mypy.--
156196 def test_values() -> {"int": int, "str": str}:
157197 return {"int": 42, "str": "test"}
158198
159- Pyright has added support in version `1.1.297 `_ (using :class: `dict `), but was later
160- removed in version `1.1.366 `_.
199+ Pyright added support for the new syntax in version `1.1.387 `_.
161200
162- .. _1.1.297 : https://github.com/microsoft/pyright/releases/tag/1.1.297
163- .. _1.1.366 : https://github.com/microsoft/pyright/releases/tag/1.1.366
201+ .. _1.1.387 : https://github.com/microsoft/pyright/releases/tag/1.1.387
164202
165203Runtime implementation
166204----------------------
@@ -203,6 +241,24 @@ While this would avoid having to import :class:`~typing.TypedDict` from
203241* If future work extends what inlined typed dictionaries can do, we don't have
204242 to worry about impact of sharing the symbol with :class: `dict `.
205243
244+ Using a simple dictionary
245+ -------------------------
246+
247+ Instead of subscripting the :class: `~typing.TypedDict ` class, a plain
248+ dictionary could be used as an annotation::
249+
250+ def get_movie() -> {'title': str}: ...
251+
252+ However, :pep: `584 ` added union operators on dictionaries and :pep: `604 `
253+ introduced :ref: `union types <python:types-union >`. Both features make use of
254+ the :ref: `bitwise or (|) <python:bitwise >` operator, making the following use
255+ cases incompatible, especially for runtime introspection::
256+
257+ # Dictionaries are merged:
258+ def fn() -> {'a': int} | {'b': str}: ...
259+
260+ # Raises a type error at runtime:
261+ def fn() -> {'a': int} | int: ...
206262
207263Open Issues
208264===========
@@ -220,6 +276,24 @@ Should we allow the following?::
220276 class SubTD(InlinedTD):
221277 pass
222278
279+ Using ``typing.Dict `` with a single argument
280+ --------------------------------------------
281+
282+ While using :class: `dict ` isn't ideal, we could make use of
283+ :class: `typing.Dict ` with a single argument::
284+
285+ def get_movie() -> Dict[{'title': str}]: ...
286+
287+ It is less verbose, doesn't have the baggage of :class: `dict `,
288+ and is defined as some kind of special form (an alias to the built-in
289+ ``dict ``).
290+
291+ However, it is currently marked as deprecated (although not scheduled for
292+ removal), so it might be confusing to undeprecate it.
293+
294+ This would also set a precedent on typing constructs being parametrizable
295+ with a different number of type arguments.
296+
223297
224298Copyright
225299=========
0 commit comments