Skip to content

Commit c298dc8

Browse files
authored
PEP 764: Updates from discussion (#4270)
1 parent a112b98 commit c298dc8

File tree

2 files changed

+63
-48
lines changed

2 files changed

+63
-48
lines changed

peps/pep-0728.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ must be assignable to the value of ``extra_items`` defined on ``MovieBase``.
237237

238238
Movie = TypedDict("Movie", {"name": str}, extra_items=int | None)
239239

240+
.. _typed-dict-closed:
241+
240242
The ``closed`` Class Parameter
241243
------------------------------
242244

peps/pep-0764.rst

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ PEP: 764
22
Title: Inlined typed dictionaries
33
Author: Victorien Plot <[email protected]>
44
Sponsor: Eric Traut <erictr at microsoft.com>
5+
Discussions-To: https://discuss.python.org/t/78779
56
Status: Draft
67
Type: Standards Track
78
Topic: Typing
89
Created: 25-Oct-2024
910
Python-Version: 3.14
11+
Post-History: `29-Jan-2025 <https://discuss.python.org/t/78779>`__
1012

1113

1214
Abstract
@@ -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-
7877
While less useful (as the functional or even the class-based syntax can be
7978
used), 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::
8786
Specification
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
9291
semantics as the :ref:`functional syntax <typing:typeddict-functional-syntax>`
9392
(the dictionary keys are strings representing the field names, and values are
9493
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
9897
assigned a :class:`dict` instance).
9998

10099
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>`_
102101
section).
103102

104103
It 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

111110
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::
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

140144
Typing specification changes
141145
----------------------------
142146

143147
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
145149
:external+typing:token:`~expression-grammar:type_expression` production will
146150
be updated to include the inlined syntax:
147151

@@ -186,8 +190,20 @@ How to Teach This
186190
The new inlined syntax will be documented both in the :mod:`typing` module
187191
documentation 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

193209
Reference Implementation
@@ -223,11 +239,11 @@ various reasons (expensive to process, evaluating them is not standardized).
223239

224240
This 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+
246266
Using 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

305308
Should 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
320323
introspection 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+
322334
Inlined 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

330343
Copyright

0 commit comments

Comments
 (0)