PEP 589 defines notation for declaring a TypedDict with all required keys and notation for defining a TypedDict with all potentially-missing keys, however it does not provide a mechanism to declare some keys as required and others as potentially-missing. This PEP introduces two new notations: Required[], which can be used on individual items of a TypedDict to mark them as required, and NotRequired[], which can be used on individual items to mark them as potentially-missing.
This PEP makes no Python grammar changes. Correct usage of required and potentially-missing keys of TypedDicts is intended to be enforced only by static type checkers and need not be enforced by Python itself at runtime.
It is not uncommon to want to define a TypedDict with some keys that are required and others that are potentially-missing. Currently the only way to define such a TypedDict is to declare one TypedDict with one value for total and then inherit it from another TypedDict with a different value for total:
Having to declare two different TypedDict types for this purpose is cumbersome.
This PEP introduces two new type qualifiers, typing.Required and typing.NotRequired, which allow defining a single TypedDict with a mix of both required and potentially-missing keys:
This PEP also makes it possible to define TypedDicts in the alternative functional syntax with a mix of required and potentially-missing keys, which is not currently possible at all because the alternative syntax does not support inheritance:
One might think it unusual to propose notation that prioritizes marking required keys rather than potentially-missing keys, as is customary in other languages like TypeScript:
The difficulty is that the best word for marking a potentially-missing key, Optional[], is already used in Python for a completely different purpose: marking values that could be either of a particular type or None. In particular the following does not work:
Attempting to use any synonym of “optional” to mark potentially-missing keys (like Missing[]) would be too similar to Optional[] and be easy to confuse with it.
Thus it was decided to focus on positive-form phrasing for required keys instead, which is straightforward to spell as Required[].
Nevertheless it is common for folks wanting to extend a regular (total=True) TypedDict to only want to add a small number of potentially-missing keys, which necessitates a way to mark keys that are not required and potentially-missing, and so we also allow the NotRequired[] form for that case.
The typing.Required type qualifier is used to indicate that a variable declared in a TypedDict definition is a required key:
Additionally the typing.NotRequired type qualifier is used to indicate that a variable declared in a TypedDict definition is a potentially-missing key:
It is an error to use Required[] or NotRequired[] in any location that is not an item of a TypedDict. Type checkers must enforce this restriction.
It is valid to use Required[] and NotRequired[] even for items where it is redundant, to enable additional explicitness if desired:
It is an error to use both Required[] and NotRequired[] at the same time:
Type checkers must enforce this restriction. The runtime implementations of Required[] and NotRequired[] may also enforce this restriction.
The alternative functional syntax for TypedDict also supports Required[] and NotRequired[]:
Any PEP 589-style TypedDict declared with total=False is equivalent to a TypedDict with an implicit total=True definition with all of its keys marked as NotRequired[].
Therefore:
is equivalent to:
Required[] and NotRequired[] can be used with Annotated[], in any nesting order:
In particular allowing Annotated[] to be the outermost annotation for an item allows better interoperability with non-typing uses of annotations, which may always want Annotated[] as the outermost annotation. [3]
typing.get_type_hints(...) applied to a TypedDict will by default strip out any Required[] or NotRequired[] type qualifiers, since these qualifiers are expected to be inconvenient for code casually introspecting type annotations.
typing.get_type_hints(..., include_extras=True) however will retain Required[] and NotRequired[] type qualifiers, for advanced code introspecting type annotations that wishes to preserve all annotations in the original source:
typing.get_origin() and typing.get_args() will be updated to recognize Required[] and NotRequired[]:
An item marked with Required[] will always appear in the __required_keys__ for its enclosing TypedDict. Similarly an item marked with NotRequired[] will always appear in __optional_keys__.
No backward incompatible changes are made by this PEP.
To define a TypedDict where most keys are required and some are potentially-missing, define a single TypedDict as normal (without the total keyword) and mark those few keys that are potentially-missing with NotRequired[].
To define a TypedDict where most keys are potentially-missing and a few are required, define a total=False TypedDict and mark those few keys that are required with Required[].
If some items accept None in addition to a regular value, it is recommended that the TYPE|None notation be preferred over Optional[TYPE] for marking such item values, to avoid using Required[] or NotRequired[] alongside Optional[] within the same TypedDict definition:
Yes:
Okay (required for Python 3.5.3-3.6):
No:
If your code supports Python <3.11 and wishes to use Required[] or NotRequired[] then it should use typing_extensions.TypedDict rather than typing.TypedDict because the latter will not understand (Not)Required[]. In particular __required_keys__ and __optional_keys__ on the resulting TypedDict type will not be correct:
Yes (Python 3.11+ only):
Yes (Python <3.11 and 3.11+):
No (Python <3.11 and 3.11+):
The mypy 0.930, pyright 1.1.117, and pyanalyze 0.4.0 type checkers support Required and NotRequired.
A reference implementation of the runtime component is provided in the typing_extensions module.
This notation would require Python grammar changes and it is not believed that marking TypedDict items as required or potentially-missing would meet the high bar required to make such grammar changes.
This notation causes Optional[] to take on different meanings depending on where it is positioned, which is inconsistent and confusing.
Also, “let’s just not put funny syntax before the colon.” [1]
We could use unary + as shorthand to mark a required key, unary - to mark a potentially-missing key, or unary ~ to mark a key with opposite-of-normal totality:
Such operators could be implemented on type via the __pos__, __neg__ and __invert__ special methods without modifying the grammar.
It was decided that it would be prudent to introduce long-form notation (i.e. Required[] and NotRequired[]) before introducing any short-form notation. Future PEPs may reconsider introducing this or other short-form notation options.
Note when reconsidering introducing this short-form notation that +, -, and ~ already have existing meanings in the Python typing world: covariant, contravariant, and invariant:
We could introduce a new type-level constant which signals the absence of a value when used as a union member, similar to JavaScript’s undefined type, perhaps called Missing:
Such a Missing constant could also be used for other scenarios such as the type of a variable which is only conditionally defined:
However this use of ...|Missing, equivalent to Union[..., Missing], doesn’t align well with what a union normally means: Union[...] always describes the type of a value that is present. By contrast missingness or non-totality is a property of a variable instead. Current precedent for marking properties of a variable include Final[...] and ClassVar[...], which the proposal for Required[...] is aligned with.
Furthermore the use of Union[..., Missing] doesn’t align with the usual ways that union values are broken down: Normally you can eliminate components of a union type using isinstance checks:
However if we were to allow Union[..., Missing] you’d either have to eliminate the Missing case with hasattr for object attributes:
or a check against locals() for local variables:
or a check via other means, such as against globals() for global variables:
Weird and inconsistent. Missing is not really a value at all; it’s an absence of definition and such an absence should be treated specially.
Eric Traut from the Pyright type checker team has stated that implementing a Union[..., Missing]-style notation would be difficult. [2]
Defining a new Missing type-level constant would be very close to introducing a new Missing value-level constant at runtime, creating a second null-like runtime value in addition to None. Having two different null-like constants in Python (None and Missing) would be confusing. Many newcomers to JavaScript already have difficulty distinguishing between its analogous constants null and undefined.
Optional[] is too ubiquitous to deprecate, although use of it may fade over time in favor of the T|None notation specified by PEP 604.
Consider the use of a special flag on a TypedDict definition to alter the interpretation of Optional inside the TypedDict to mean “optional item” rather than its usual meaning of “nullable”:
or:
This would add more confusion for users because it would mean that in some contexts the meaning of Optional[] is different than in other contexts, and it would be easy to overlook the flag.
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source: https://github.com/python/peps/blob/main/peps/pep-0655.rst
Last modified: 2024-06-16 22:42:44 UTC