-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
PEP 764: Second round of updates #4386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||
| PEP: 764 | ||||||
| Title: Inlined typed dictionaries | ||||||
| Title: Inline typed dictionaries | ||||||
| Author: Victorien Plot <[email protected]> | ||||||
| Sponsor: Eric Traut <erictr at microsoft.com> | ||||||
| Discussions-To: https://discuss.python.org/t/78779 | ||||||
|
|
@@ -20,7 +20,7 @@ typed dictionaries. In both scenarios, it requires defining a class or | |||||
| assigning to a value. In some situations, this can add unnecessary | ||||||
| boilerplate, especially if the typed dictionary is only used once. | ||||||
|
|
||||||
| This PEP proposes the addition of a new inlined syntax, by subscripting the | ||||||
| This PEP proposes the addition of a new inline syntax, by subscripting the | ||||||
| :class:`~typing.TypedDict` type:: | ||||||
|
|
||||||
| from typing import TypedDict | ||||||
|
|
@@ -69,17 +69,17 @@ Taking a simple function returning some nested structured data as an example:: | |||||
| Rationale | ||||||
| ========= | ||||||
|
|
||||||
| The new inlined syntax can be used to resolve these problems:: | ||||||
| The new inline syntax can be used to resolve these problems:: | ||||||
|
|
||||||
| def get_movie() -> TypedDict[{'name': str, 'year': int, 'production': TypedDict[{'name': str, 'location': str}]}]: | ||||||
| ... | ||||||
|
|
||||||
| While less useful (as the functional or even the class-based syntax can be | ||||||
| used), inlined typed dictionaries can be assigned to a variable, as an alias:: | ||||||
| used), inline type dictionaries can be assigned to a variable, as an alias:: | ||||||
|
|
||||||
| InlinedTD = TypedDict[{'name': str}] | ||||||
| InlineTD = TypedDict[{'name': str}] | ||||||
|
|
||||||
| def get_movie() -> InlinedTD: | ||||||
| def get_movie() -> InlineTD: | ||||||
| ... | ||||||
|
|
||||||
|
|
||||||
|
|
@@ -96,11 +96,11 @@ comma-separated list of ``key: value`` pairs within braces constructor | |||||
| argument (i.e. it is not allowed to use a variable which was previously | ||||||
| assigned a :class:`dict` instance). | ||||||
|
|
||||||
| Inlined typed dictionaries can be referred to as *anonymous*, meaning they | ||||||
| Inline typed dictionaries can be referred to as *anonymous*, meaning they | ||||||
| don't have a specific name (see the `runtime behavior <Runtime behavior>`_ | ||||||
| section). | ||||||
|
|
||||||
| It is possible to define a nested inlined dictionary:: | ||||||
| It is possible to define a nested inline dictionary:: | ||||||
|
|
||||||
| Movie = TypedDict[{'name': str, 'production': TypedDict[{'location': str}]}] | ||||||
|
|
||||||
|
|
@@ -112,65 +112,75 @@ any :term:`typing:type qualifier` can be used for individual fields:: | |||||
|
|
||||||
| Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}] | ||||||
|
|
||||||
| Inlined typed dictionaries are implicitly *total*, meaning all keys must be | ||||||
| Inline typed dictionaries are implicitly *total*, meaning all keys must be | ||||||
| present. Using the :data:`~typing.Required` type qualifier is thus redundant. | ||||||
|
|
||||||
| Type variables are allowed in inlined typed dictionaries, provided that they | ||||||
| Type variables are allowed in inline typed dictionaries, provided that they | ||||||
| are bound to some outer scope:: | ||||||
|
|
||||||
| class C[T]: | ||||||
| inlined_td: TypedDict[{'name': T}] # OK, `T` is scoped to the class `C`. | ||||||
| inline_td: TypedDict[{'name': T}] # OK, `T` is scoped to the class `C`. | ||||||
|
|
||||||
| reveal_type(C[int]().inlined_td['name']) # Revealed type is 'int' | ||||||
| reveal_type(C[int]().inline_td['name']) # Revealed type is 'int' | ||||||
|
|
||||||
|
|
||||||
| def fn[T](arg: T) -> TypedDict[{'name': T}]: ... # OK: `T` is scoped to the function `fn`. | ||||||
|
|
||||||
| reveal_type(fn('a')['name']) # Revealed type is 'str' | ||||||
|
|
||||||
|
|
||||||
| type InlinedTD[T] = TypedDict[{'name': T}] # OK, `T` is scoped to the type alias. | ||||||
| type InlineTD[T] = TypedDict[{'name': T}] # OK, `T` is scoped to the type alias. | ||||||
|
|
||||||
|
|
||||||
| T = TypeVar('T') | ||||||
|
|
||||||
| InlinedTD = TypedDict[{'name': T}] # OK, same as the previous type alias, but using the old-style syntax. | ||||||
| InlineTD = TypedDict[{'name': T}] # OK, same as the previous type alias, but using the old-style syntax. | ||||||
|
|
||||||
|
|
||||||
| def func(): | ||||||
| InlinedTD = TypedDict[{'name': T}] # Not OK: `T` refers to a type variable that is not bound to the scope of `func`. | ||||||
| InlineTD = TypedDict[{'name': T}] # Not OK: `T` refers to a type variable that is not bound to the scope of `func`. | ||||||
|
|
||||||
|
|
||||||
| Inline typed dictionaries can be extended:: | ||||||
|
|
||||||
| InlineTD = TypedDict[{'a': int}] | ||||||
|
|
||||||
| class SubTD(InlineTD): | ||||||
| pass | ||||||
|
|
||||||
| Typing specification changes | ||||||
| ---------------------------- | ||||||
|
|
||||||
| The inlined typed dictionary adds a new kind of | ||||||
| The inline typed dictionary adds a new kind of | ||||||
| :term:`typing:type expression`. As such, the | ||||||
| :external+typing:token:`~expression-grammar:type_expression` production will | ||||||
| be updated to include the inlined syntax: | ||||||
| be updated to include the inline syntax: | ||||||
|
|
||||||
| .. productionlist:: inlined-typed-dictionaries-grammar | ||||||
| .. productionlist:: inline-typed-dictionaries-grammar | ||||||
| new-type_expression: `~expression-grammar:type_expression` | ||||||
| : | <TypedDict> '[' '{' (string: ':' `~expression-grammar:annotation_expression` ',')* '}' ']' | ||||||
| : (where string is any string literal) | ||||||
|
|
||||||
| Runtime behavior | ||||||
| ---------------- | ||||||
|
|
||||||
| Although :class:`~typing.TypedDict` is commonly referred as a class, it is | ||||||
| implemented as a function at runtime. To be made subscriptable, it will be | ||||||
| changed to be a class. | ||||||
|
|
||||||
| Creating an inlined typed dictionary results in a new class, so ``T1`` and | ||||||
| Creating an inline typed dictionary results in a new class, so ``T1`` and | ||||||
| ``T2`` are of the same type:: | ||||||
|
|
||||||
| from typing import TypedDict | ||||||
|
|
||||||
| T1 = TypedDict('T1', {'a': int}) | ||||||
| T2 = TypedDict[{'a': int}] | ||||||
|
|
||||||
| As inlined typed dictionaries are are meant to be *anonymous*, their | ||||||
| :attr:`~type.__name__` attribute will be set to an empty string. | ||||||
| As inline typed dictionaries are meant to be *anonymous*, their | ||||||
| :attr:`~type.__name__` attribute will be set to the ``<inline TypedDict>`` | ||||||
| string literal. In the future, an explicit class attribute could be added | ||||||
| to make them distinguishable from named classes. | ||||||
|
|
||||||
| Although :class:`~typing.TypedDict` is documented as a class, the way it is | ||||||
| defined is an implementation detail. The implementation will have to be tweaked | ||||||
| so that :class:`~typing.TypedDict` can be made subscriptable. | ||||||
|
|
||||||
|
|
||||||
| Backwards Compatibility | ||||||
| ======================= | ||||||
|
|
@@ -187,12 +197,12 @@ There are no known security consequences arising from this PEP. | |||||
| How to Teach This | ||||||
| ================= | ||||||
|
|
||||||
| The new inlined syntax will be documented both in the :mod:`typing` module | ||||||
| The new inline syntax will be documented both in the :mod:`typing` module | ||||||
| documentation and the :ref:`typing specification <typing:typed-dictionaries>`. | ||||||
|
|
||||||
| When complex dictionary structures are used, having everything defined on a | ||||||
| single line can hurt readability. Code formatters can help by formatting the | ||||||
| inlined typed dictionary across multiple lines:: | ||||||
| inline type dictionary across multiple lines:: | ||||||
|
|
||||||
| def edit_movie( | ||||||
| movie: TypedDict[{ | ||||||
|
|
@@ -214,14 +224,18 @@ Mypy supports a similar syntax as an :option:`experimental feature <mypy:mypy.-- | |||||
| def test_values() -> {"int": int, "str": str}: | ||||||
| return {"int": 42, "str": "test"} | ||||||
|
|
||||||
| Support for this PEP is added in `this pull request <https://github.com/python/mypy/pull/18889>`_. | ||||||
|
||||||
| Support for this PEP is added in `this pull request <https://github.com/python/mypy/pull/18889>`_. | |
| Support for this PEP is added in `this pull request <https://github.com/python/mypy/pull/18889>`__. |
Plus we could use more accessible link text instead:
https://www.a11yproject.com/posts/creating-valid-and-accessible-links/#write-helpful-link-text
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have suggestions to make it more accessible? The way I understand what's described in your link is that the link text should describe what it points to, and this pull request seems to describe the target correctly? (Perhaps mypy should be mentioned in the text).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If someone is navigating just via links, they'll hear "this pull request", "this pull request" for both.
Perhaps we could mention the repo names? For example, "this mypy pull request", "this typing_extensions pull request".
Uh oh!
There was an error while loading. Please reload this page.