PEP 585: Type Hinting Generics In Standard Collections
list[str]
Read in the browser: https://www.python.org/dev/peps/pep-0585/ <https://www.python.org/dev/peps/pep-0585/> Read the source: https://raw.githubusercontent.com/python/peps/master/pep-0585.rst <https://raw.githubusercontent.com/python/peps/master/pep-0585.rst> The following PEP has been discussed on typing-sig already and a prototype implementation exists for it. I'm extending it now for wider feedback on python-dev, with the intent to present the final version for the Steering Council's consideration by mid-March. PEP: 585 Title: Type Hinting Generics In Standard Collections Author: Łukasz Langa <lukasz@python.org> Discussions-To: Typing-Sig <typing-sig@python.org> Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 03-Mar-2019 Python-Version: 3.9 Abstract Static typing as defined by PEPs 484, 526, 544, 560, and 563 was built incrementally on top of the existing Python runtime and constrained by existing syntax and runtime behavior. This led to the existence of a duplicated collection hierarchy in the typing module due to generics (for example typing.List and the built-in list). This PEP proposes to enable support for the generics syntax in all standard collections currently available in the typing module. Rationale and Goals This change removes the necessity for a parallel type hierarchy in the typing module, making it easier for users to annotate their programs and easier for teachers to teach Python. Terminology Generic (n.) -- a type that can be parametrized, typically a container. Also known as a parametric type or a generic type. For example: dict. Parametrized generic -- a specific instance of a generic with the expected types for container elements provided. Also known as a parametrized type. For example: dict[str, int]. Backwards compatibility Tooling, including type checkers and linters, will have to be adapted to recognize standard collections as generics. On the source level, the newly described functionality requires Python 3.9. For use cases restricted to type annotations, Python files with the "annotations" future-import (available since Python 3.7) can parametrize standard collections, including builtins. To reiterate, that depends on the external tools understanding that this is valid. Implementation Starting with Python 3.7, when from __future__ import annotations is used, function and variable annotations can parametrize standard collections directly. Example: from __future__ import annotations def find(haystack: dict[str, list[int]]) -> int: ... Usefulness of this syntax before PEP 585 <https://www.python.org/dev/peps/pep-0585> is limited as external tooling like Mypy does not recognize standard collections as generic. Moreover, certain features of typing like type aliases or casting require putting types outside of annotations, in runtime context. While these are relatively less common than type annotations, it's important to allow using the same type syntax in all contexts. This is why starting with Python 3.9, the following collections become generic using __class_getitem__() to parametrize contained types: tuple # typing.Tuple list # typing.List dict # typing.Dict set # typing.Set frozenset # typing.FrozenSet type # typing.Type collections.deque collections.defaultdict collections.OrderedDict collections.Counter collections.ChainMap collections.abc.Awaitable collections.abc.Coroutine collections.abc.AsyncIterable collections.abc.AsyncIterator collections.abc.AsyncGenerator collections.abc.Iterable collections.abc.Iterator collections.abc.Generator collections.abc.Reversible collections.abc.Container collections.abc.Collection collections.abc.Callable collections.abc.Set # typing.AbstractSet collections.abc.MutableSet collections.abc.Mapping collections.abc.MutableMapping collections.abc.Sequence collections.abc.MutableSequence collections.abc.ByteString collections.abc.MappingView collections.abc.KeysView collections.abc.ItemsView collections.abc.ValuesView contextlib.AbstractContextManager # typing.ContextManager contextlib.AbstractAsyncContextManager # typing.AsyncContextManager re.Pattern # typing.Pattern, typing.re.Pattern re.Match # typing.Match, typing.re.Match Importing those from typing is deprecated. Due to PEP 563 <https://www.python.org/dev/peps/pep-0563> and the intention to minimize the runtime impact of typing, this deprecation will not generate DeprecationWarnings. Instead, type checkers may warn about such deprecated usage when the target version of the checked program is signalled to be Python 3.9 or newer. It's recommended to allow for those warnings to be silenced on a project-wide basis. The deprecated functionality will be removed from the typing module in the first Python version released 5 years after the release of Python 3.9.0. Parameters to generics are available at runtime Preserving the generic type at runtime enables introspection of the type which can be used for API generation or runtime type checking. Such usage is already present in the wild. Just like with the typing module today, the parametrized generic types listed in the previous section all preserve their type parameters at runtime: list[str]
tuple[int, ...] tuple[int, ...] ChainMap[str, list[str]]
collections.ChainMap[str, list[str]] This is implemented using a thin proxy type that forwards all method calls and attribute accesses to the bare origin type with the following exceptions:
the __repr__ shows the parametrized type; the __origin__ attribute points at the non-parametrized generic class; the __args__ attribute is a tuple (possibly of length 1) of generic types passed to the original __class_getitem__; the __parameters__ attribute is a lazily computed tuple (possibly empty) of unique type variables found in __args__; the __getitem__ raises an exception to disallow mistakes like dict[str][str]. However it allows e.g. dict[str, T][int] and in that case returns dict[str, int]. This design means that it is possible to create instances of parametrized collections, like:
l = list[str]() [] list is list[str] False list == list[str] False list[str] == list[str] True list[str] == list[int] False isinstance([1, 2, 3], list[str]) TypeError: isinstance() arg 2 cannot be a parametrized generic issubclass(list, list[str]) TypeError: issubclass() arg 2 cannot be a parametrized generic isinstance(list[str], types.GenericAlias) True Objects created with bare types and parametrized types are exactly the same. The generic parameters are not preserved in instances created with parametrized types, in other words generic types erase type parameters during object creation.
One important consequence of this is that the interpreter does not attempt to type check operations on the collection created with a parametrized type. This provides symmetry between: l: list[str] = [] and: l = list[str]() For accessing the proxy type from Python code, it will be exported from the types module as GenericAlias. Pickling or (shallow- or deep-) copying a GenericAlias instance will preserve the type, origin, attributes and parameters. Forward compatibility Future standard collections must implement the same behavior. Reference implementation A proof-of-concept or prototype implementation <https://bugs.python.org/issue39481> exists. Rejected alternatives Do nothing Keeping the status quo forces Python programmers to perform book-keeping of imports from the typing module for standard collections, making all but the simplest annotations cumbersome to maintain. The existence of parallel types is confusing to newcomers (why is there both list and List?). The above problems also don't exist in user-built generic classes which share runtime functionality and the ability to use them as generic type annotations. Making standard collections harder to use in type hinting from user classes hindered typing adoption and usability. Generics erasure It would be easier to implement __class_getitem__ on the listed standard collections in a way that doesn't preserve the generic type, in other words:
list[str] <class 'list'> tuple[int, ...] <class 'tuple'> collections.ChainMap[str, list[str]] <class 'collections.ChainMap'> This is problematic as it breaks backwards compatibility: current equivalents of those types in the typing module do preserve the generic type:
from typing import List, Tuple, ChainMap List[str] typing.List[str] Tuple[int, ...] typing.Tuple[int, ...] ChainMap[str, List[str]] typing.ChainMap[str, typing.List[str]] As mentioned in the "Implementation" section, preserving the generic type at runtime enables runtime introspection of the type which can be used for API generation or runtime type checking. Such usage is already present in the wild.
Additionally, implementing subscripts as identity functions would make Python less friendly to beginners. Say, if a user is mistakenly passing a list type instead of a list object to a function, and that function is indexing the received object, the code would no longer raise an error. Today:
l = list l[-1] TypeError: 'type' object is not subscriptable With __class_getitem__ as an identity function:
l = list l[-1] list The indexing being successful here would likely end up raising an exception at a distance, confusing the user.
Disallowing instantiation of parametrized types Given that the proxy type which preserves __origin__ and __args__ is mostly useful for runtime introspection purposes, we might have disallowed instantiation of parametrized types. In fact, forbidding instantiation of parametrized types is what the typing module does today for types which parallel builtin collections (instantiation of other parametrized types is allowed). The original reason for this decision was to discourage spurious parametrization which made object creation up to two orders of magnitude slower compared to the special syntax available for those builtin collections. This rationale is not strong enough to allow the exceptional treatment of builtins. All other parametrized types can be instantiated, including parallels of collections in the standard library. Moreover, Python allows for instantiation of lists using list() and some builtin collections don't provide special syntax for instantiation. Making isinstance(obj, list[str]) perform a check ignoring generics An earlier version of this PEP suggested treating parametrized generics like list[str] as equivalent to their non-parametrized variants like list for purposes of isinstance() and issubclass(). This would be symmetrical to how list[str]() creates a regular list. This design was rejected because isinstance() and issubclass() checks with parametrized generics would read like element-by-element runtime type checks. The result of those checks would be surprising, for example:
isinstance([1, 2, 3], list[str]) True Note the object doesn't match the provided generic type but isinstance() still returns True because it only checks whether the object is a list.
If a library is faced with a parametrized generic and would like to perform an isinstance() check using the base type, that type can be retrieved using the __origin__ attribute on the parametrized generic. Making isinstance(obj, list[str]) perform a runtime type check This functionality requires iterating over the collection which is a destructive operation in some of them. This functionality would have been useful, however implementing the type checker within Python that would deal with complex types, nested type checking, type variables, string forward references, and so on is out of scope for this PEP. Note on the initial draft An early version of this PEP discussed matters beyond generics in standard collections. Those unrelated topics were removed for clarity. Copyright This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
This looks like a nice usability improvement to me. My only suggestion would be that types.MappingProxyType be included on the list of types to be updated. Cheers, Nick.
While working on the implementation with Guido I made a list of things that inherit from typing.Generic in typeshed that haven't been listed/implemented yet. https://github.com/gvanrossum/cpython/pull/1#issuecomment-582781121 On Sat, Feb 22, 2020, 3:50 PM Nick Coghlan <ncoghlan@gmail.com> wrote:
This looks like a nice usability improvement to me.
My only suggestion would be that types.MappingProxyType be included on the list of types to be updated.
Cheers, Nick. _______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EYU3VDK7... Code of Conduct: http://python.org/psf/codeofconduct/
This issue might be useful about it: https://bugs.python.org/issue39019 On Sun, Feb 23, 2020 at 2:12 PM Ethan Smith <ethan@ethanhs.me> wrote:
While working on the implementation with Guido I made a list of things that inherit from typing.Generic in typeshed that haven't been listed/implemented yet.
https://github.com/gvanrossum/cpython/pull/1#issuecomment-582781121
On Sat, Feb 22, 2020, 3:50 PM Nick Coghlan <ncoghlan@gmail.com> wrote:
This looks like a nice usability improvement to me.
My only suggestion would be that types.MappingProxyType be included on the list of types to be updated.
Cheers, Nick. _______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EYU3VDK7... Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/Z47AGWUR... Code of Conduct: http://python.org/psf/codeofconduct/
I can't find it right now, but IIRC somebody commented that "GenericAlias" is a somewhat odd name. I didn't spend much time thinking about the name, I just took it from `typing._GenericAlias` (which has a similar role). It would be hard to change the name later. ATM it's one global substitute on my branch. Should we change it? To what? -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>
The discussion on the name change came from Łukasz https://github.com/python/cpython/pull/18239#discussion_r380996908 I suggested "GenericType" to be in line with other things in types.py. On Mon, Feb 24, 2020 at 8:39 PM Guido van Rossum <guido@python.org> wrote:
I can't find it right now, but IIRC somebody commented that "GenericAlias" is a somewhat odd name. I didn't spend much time thinking about the name, I just took it from `typing._GenericAlias` (which has a similar role).
It would be hard to change the name later. ATM it's one global substitute on my branch. Should we change it? To what?
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>
On Tue, 25 Feb 2020 at 15:19, Ethan Smith <ethan@ethanhs.me> wrote:
The discussion on the name change came from Łukasz https://github.com/python/cpython/pull/18239#discussion_r380996908
I suggested "GenericType" to be in line with other things in types.py.
Quoting Łukasz question: "I know it's late for this bikeshedding but I was always a bit puzzled by the name "GenericAlias". What is it aliasing?" The "GenericAlias" name seemed appropriate to me as these aren't real types - they're aliases for the corresponding container type with some extra metadata attached. So "list[str]" is *mostly* just a different way of writing "list" at runtime - it's primarily typecheckers that will treat it differently (while the runtime typechecking machinery will reject it as too specific to be checked non-destructively). "GenericAliasForAConcreteContainerType" would be excessively wordy though, hence "GenericAlias". By contrast, I'd expect something called "GenericType" to actually be able to do full runtime typechecking and enforcement (e.g. having instances throw TypeError if you tried to insert a value of the wrong type). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
OK, it's certainly easier *not* to change it, so I'm happy with this argument for the current name. Łukasz, do you agree? Then I think we can submit this to the SC for acceptance. On Tue, Feb 25, 2020 at 6:27 AM Nick Coghlan <ncoghlan@gmail.com> wrote:
On Tue, 25 Feb 2020 at 15:19, Ethan Smith <ethan@ethanhs.me> wrote:
The discussion on the name change came from Łukasz
https://github.com/python/cpython/pull/18239#discussion_r380996908
I suggested "GenericType" to be in line with other things in types.py.
Quoting Łukasz question: "I know it's late for this bikeshedding but I was always a bit puzzled by the name "GenericAlias". What is it aliasing?"
The "GenericAlias" name seemed appropriate to me as these aren't real types - they're aliases for the corresponding container type with some extra metadata attached. So "list[str]" is *mostly* just a different way of writing "list" at runtime - it's primarily typecheckers that will treat it differently (while the runtime typechecking machinery will reject it as too specific to be checked non-destructively).
"GenericAliasForAConcreteContainerType" would be excessively wordy though, hence "GenericAlias".
By contrast, I'd expect something called "GenericType" to actually be able to do full runtime typechecking and enforcement (e.g. having instances throw TypeError if you tried to insert a value of the wrong type).
Cheers, Nick.
-- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>
This now happened. Maybe you can just submit it to the SC for review? Or did that already happen? (I no longer have any insight into the SC's queue.) On Tue, Feb 25, 2020 at 2:40 PM Łukasz Langa <lukasz@langa.pl> wrote:
On 25 Feb 2020, at 16:22, Guido van Rossum <guido@python.org> wrote:
Łukasz, do you agree?
As long as we include a form of Nick's explanation in the docs for the type, I'm fine with that.
- Ł
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>
Guido van Rossum writes:
This now happened. Maybe you can just submit it to the SC for review? Or did that already happen? (I no longer have any insight into the SC's queue.)
Shouldn't the fact that it's been submitted be public? As a status value in the header material of the PEP itself, for example. The SC's internal review process is no business of mine (IMO), but the fact of submission is in some sense "property" of the proponent, no?
Maybe this can be done by using the public SC issue tracker: https://mail.google.com/mail/u/0/#inbox/FMfcgxwHMPfZfZKJSsbVCFPXmLBQlHQd On Sat, Mar 14, 2020 at 2:01 AM Stephen J. Turnbull < turnbull.stephen.fw@u.tsukuba.ac.jp> wrote:
Guido van Rossum writes:
This now happened. Maybe you can just submit it to the SC for review? Or did that already happen? (I no longer have any insight into the SC's queue.)
Shouldn't the fact that it's been submitted be public? As a status value in the header material of the PEP itself, for example. The SC's internal review process is no business of mine (IMO), but the fact of submission is in some sense "property" of the proponent, no?
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>
On Sat, Mar 14, 2020 at 11:55 AM Guido van Rossum <guido@python.org> wrote:
Maybe this can be done by using the public SC issue tracker: https://mail.google.com/mail/u/0/#inbox/FMfcgxwHMPfZfZKJSsbVCFPXmLBQlHQd
That looks like a private Gmail link, possibly a copy-and-paste mixup. Can you give the correct link?
Whoops. Here it is: https://github.com/python/steering-council/issues Tested in a private browsing window. On Sat, Mar 14, 2020 at 1:21 PM Jonathan Goble <jcgoble3@gmail.com> wrote:
On Sat, Mar 14, 2020 at 11:55 AM Guido van Rossum <guido@python.org> wrote:
Maybe this can be done by using the public SC issue tracker: https://mail.google.com/mail/u/0/#inbox/FMfcgxwHMPfZfZKJSsbVCFPXmLBQlHQd
That looks like a private Gmail link, possibly a copy-and-paste mixup. Can you give the correct link?
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>
I submitted a request for consideration to the Steering Council: https://github.com/python/steering-council/issues/21 - Ł
On 14 Mar 2020, at 21:30, Guido van Rossum <guido@python.org> wrote:
Whoops. Here it is: https://github.com/python/steering-council/issues <https://github.com/python/steering-council/issues>
Tested in a private browsing window.
On Sat, Mar 14, 2020 at 1:21 PM Jonathan Goble <jcgoble3@gmail.com <mailto:jcgoble3@gmail.com>> wrote: On Sat, Mar 14, 2020 at 11:55 AM Guido van Rossum <guido@python.org <mailto:guido@python.org>> wrote: Maybe this can be done by using the public SC issue tracker: https://mail.google.com/mail/u/0/#inbox/FMfcgxwHMPfZfZKJSsbVCFPXmLBQlHQd <https://mail.google.com/mail/u/0/#inbox/FMfcgxwHMPfZfZKJSsbVCFPXmLBQlHQd>
That looks like a private Gmail link, possibly a copy-and-paste mixup. Can you give the correct link?
-- --Guido van Rossum (python.org/~guido <http://python.org/~guido>) Pronouns: he/him (why is my pronoun here?) <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/FSE4HBEV... Code of Conduct: http://python.org/psf/codeofconduct/
participants (7)
-
Batuhan Taskaya
-
Ethan Smith
-
Guido van Rossum
-
Jonathan Goble
-
Nick Coghlan
-
Stephen J. Turnbull
-
Łukasz Langa