You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: peps/pep-0705.rst
+43-7Lines changed: 43 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,7 +21,8 @@ Abstract
21
21
22
22
:pep:`589` defines the structural type :class:`~typing.TypedDict` for dictionaries with a fixed set of keys.
23
23
As ``TypedDict`` is a mutable type, it is difficult to correctly annotate methods which accept read-only parameters in a way that doesn't prevent valid inputs.
24
-
This PEP proposes a new type qualifier, ``typing.ReadOnly``, to support these usages.
24
+
25
+
This PEP proposes a new type qualifier, ``typing.ReadOnly``, to support these usages. It makes no Python grammar changes. Correct usage of read-only keys of TypedDicts is intended to be enforced only by static type checkers, and will not be enforced by Python itself at runtime.
25
26
26
27
Motivation
27
28
==========
@@ -125,7 +126,7 @@ It is possible to work around this issue with generics (as of Python 3.11), but
125
126
Rationale
126
127
=========
127
128
128
-
These problems can be resolved by removing the ability to update one or more of the items in a ``TypedDict``. This does not mean the items are immutable: a reference to the underlying dictionary could still exist with a different but compatible type in which those items have mutator operations. As such, these are not "final" items; using this term would risk confusion with final attributes, which are fully immutable. These items are "read-only", and we introduce a new ``typing.ReadOnly`` type qualifier for this purpose.
129
+
These problems can be resolved by removing the ability to update one or more of the items in a ``TypedDict``. This does not mean the items are immutable: a reference to the underlying dictionary could still exist with a different but compatible type in which those items have mutator operations. These items are "read-only", and we introduce a new ``typing.ReadOnly`` type qualifier for this purpose.
129
130
130
131
The ``movie_string`` function in the first motivating example can then be typed as follows::
131
132
@@ -324,7 +325,7 @@ In addition to existing type checking rules, type checkers should error if a Typ
324
325
a2: A = { "x": 3, "y": 4 }
325
326
a1.update(a2) # Type check error: "x" is read-only in A
326
327
327
-
Unless the declared value is of bottom type::
328
+
Unless the declared value is of bottom type (:data:`~typing.Never`)::
328
329
329
330
class B(TypedDict):
330
331
x: NotRequired[typing.Never]
@@ -333,6 +334,8 @@ Unless the declared value is of bottom type::
333
334
def update_a(a: A, b: B) -> None:
334
335
a.update(b) # Accepted by type checker: "x" cannot be set on b
335
336
337
+
Note: Nothing will ever match the ``Never`` type, so an item annotated with it must be absent.
338
+
336
339
Keyword argument typing
337
340
-----------------------
338
341
@@ -354,6 +357,30 @@ Keyword argument typing
354
357
355
358
fn: Function = impl # Accepted by type checker: function signatures are identical
356
359
360
+
Runtime behavior
361
+
----------------
362
+
363
+
``TypedDict`` types will gain two new attributes, ``__readonly_keys__`` and ``__mutable_keys__``, which will be frozensets containing all read-only and non-read-only keys, respectively::
``typing.get_origin`` and ``typing.get_args`` will be updated to recognize ``ReadOnly``::
380
+
381
+
assert get_origin(ReadOnly[int]) is ReadOnly
382
+
assert get_args(ReadOnly[int]) == (int,)
383
+
357
384
358
385
Backwards compatibility
359
386
=======================
@@ -368,18 +395,18 @@ There are no known security consequences arising from this PEP.
368
395
How to teach this
369
396
=================
370
397
371
-
Suggestion for changes to the :mod:`typing` module, in line with current practice:
398
+
Suggested changes to the :mod:`typing` module documentation, in line with current practice:
372
399
373
400
* Add this PEP to the others listed.
374
401
* Add ``typing.ReadOnly``, linked to TypedDict and this PEP.
375
402
* Add the following text to the TypedDict entry:
376
403
377
-
Individual items can be excluded from mutate operations using ReadOnly, allowing them to be read but not changed. This is useful when the exact type of the value is not known yet, and so modifying it would break structural subtypes. *insert example*
404
+
The ``ReadOnly`` type qualifier indicates that an item declared in a ``TypedDict`` definition may be read but not mutated (added, modified or removed). This is useful when the exact type of the value is not known yet, and so modifying it would break structural subtypes. *insert example*
378
405
379
406
Reference implementation
380
407
========================
381
408
382
-
pyright 1.1.332 fully implements this proposal.
409
+
`pyright 1.1.333 fully implements this proposal<https://github.com/microsoft/pyright/releases/tag/1.1.333>`_.
383
410
384
411
Rejected alternatives
385
412
=====================
@@ -399,6 +426,15 @@ Calling the type ``Readonly``
399
426
400
427
``Read-only`` is generally hyphenated, and it appears to be common convention to put initial caps onto words separated by a dash when converting to CamelCase. This appears consistent with the definition of CamelCase on Wikipedia: CamelCase uppercases the first letter of each word. That said, Python examples or counter-examples, ideally from the core Python libraries, or better explicit guidance on the convention, would be greatly appreciated.
401
428
429
+
Reusing the ``Final`` annotation
430
+
--------------------------------
431
+
432
+
The :class:`~typing.Final` annotation prevents an attribute from being modified, like the proposed ``ReadOnly`` qualifier does for ``TypedDict`` items. However, it is also documented as preventing redefinition in subclasses too; from :pep:`591`:
433
+
434
+
The ``typing.Final`` type qualifier is used to indicate that a variable or attribute should not be reassigned, redefined, or overridden.
435
+
436
+
This does not fit with the intended use of ``ReadOnly``. Rather than introduce confusion by having ``Final`` behave differently in different contexts, we chose to introduce a new qualifier.
437
+
402
438
A readonly flag
403
439
---------------
404
440
@@ -422,7 +458,7 @@ However, this led to confusion when inheritance was introduced::
422
458
b: B = { "key1": 1, "key2": 2 }
423
459
b["key1"] = 4 # Accepted by type checker: "key1" is not read-only
424
460
425
-
It would be reasonable for someone familiar with ``frozen``, on seeing just the definition of B, to assume that the whole type was read-only. On the other hand, it would be reasonable for someone familiar with ``total`` to assume that read-only only applies to the current type.
461
+
It would be reasonable for someone familiar with ``frozen`` (from :mod:`dataclasses`), on seeing just the definition of B, to assume that the whole type was read-only. On the other hand, it would be reasonable for someone familiar with ``total`` to assume that read-only only applies to the current type.
426
462
427
463
The original proposal attempted to eliminate this ambiguity by making it both a type check and a runtime error to define ``B`` in this way. This was still a source of surprise to people expecting it to work like ``total``.
0 commit comments