PEP 604 -- Complementary syntax for Union[]
| PEP: | 604 |
|---|---|
| Title: | Complementary syntax for Union[] |
| Author: | Philippe PRADOS <python at prados.fr> |
| Sponsor: | Chris Angelico <rosuav at gmail.com> |
| BDFL-Delegate: | Ivan Levkivskyi <levkivskyi at gmail.com> |
| Status: | Draft |
| Type: | Standards Track |
| Created: | 28-Aug-2019 |
| Python-Version: | 3.9 |
Contents
Abstract
This PEP proposes a complementary syntax for Union[X,Y] and extends its purpose to isinstance and issubclass.
Motivation
PEP 484 and PEP 526 propose a generic syntax to add typing to variables, parameters and function returns. PEP 585 proposes to expose parameters to generics at runtime. MyPy [1] accepts a syntax which looks like:
annotation: name_type
name_type: NAME (args)?
args: '[' paramslist ']'
paramslist: annotation (',' annotation)* [',']
- To describe a disjunction, the user must use Union[X,Y].
The verbosity of this syntax does not help the adoption.
Proposal
Inspired by Scala language [2] and Pike [3], this proposal adds operator type.__or__(). With this new operator, it is possible to write int | str instead of Union[int,str]. The result of this expression would then be valid in isinstance() and issubclass():
isinstance(5, int | str) issubclass(bool, int | float)
Examples
Here are some examples of what we can do with this feature.
# in place of
# def f(list: List[Union[int, str]], param: Optional[int]) -> Union[float, str]
def f(list: List[int | str], param: int | None) -> float | str:
pass
f([1, "abc"], None)
assert str | int == Union[str,int]
assert str | int | float == Union[str, int, float]
assert isinstance("", int | str)
assert issubclass(bool, int | float)
Once the Python language is extended, MyPy [1] and other type checkers will need to be updated to accept this new syntax.
Technical point of view
To accept to extend isinstance() and issubclass(), the object _GenericAlias must be available as a core, that doesn't have a directly-accessible name but via alias in typing module..
Incompatible changes
In some situations, some exceptions will not be raised as expected.
For backward compatibility, typing.py must say _GenericAlias = _GenericAlias.
If a metaclass implements the __or__ operator, it will override this:
>>> class M(type): ... def __or__(self,other): return "Hello" ... >>> class C(metaclass=M):pass ... >>> C | int 'Hello' >>> int | C typing.Union[int, __main__.C] >>> Union[C,int] typing.Union[__main__.C, int]
Objections and responses
For more details about discussions, see links below:
1. Add a new operator for Union[type1|type2]?
PROS:
- This syntax can be more readable, and is similary to others languages (Scala, ...)
- At runtime, int|str might return a simple object in 3.9, rather than everything that you'd need to grab from importing typing
CONS:
- Adding this operator introduce a dependency between typing and builtins
- As breaking the backport (in that typing can easily be backported but core types can't)
- If Python itself doesn't have to be changed, we'd still need to implement it in mypy, Pyre, PyCharm, Pytype, and who knows what else (it's a minor change see "Reference Implementation"
Change only the PEP 484 (Type hints) to accept the syntax type1 | type2 ?
PEP 563 (Postponed Evaluation of Annotations) is enough to accept this proposition, if we accept to not be compatible with the dynamic evaluation of annotations (eval()).
>>> from __future__ import annotations >>> def foo() -> int | str: pass ... >>> eval(foo.__annotations__['return']) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1, in <module> TypeError: unsupported operand type(s) for |: 'type' and 'type'
2. Extend isinstance() and issubclass() to accept Union ?
isinstance(x, str | int) ==> "is x an instance of str or int"
PROS:
- If they were permitted, then instance checking could use an extremely clean-looking notation
- The implementation can use the tuple present in Union parameter, without create a new instance
CONS:
- Must migrate all the typing module in builtin
Reference Implementation
A proposed implementation for cpython is here. A proposed implementation for mypy is here.
References
| [1] | (1, 2) MyPy http://mypy-lang.org/ |
| [2] | Scala Union Types https://dotty.epfl.ch/docs/reference/new-types/union-types.html |
| [3] | Pike http://pike.lysator.liu.se/docs/man/chapter_3.html#3.5 |
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
