|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>|
- Technical point of view
- Incompatible changes
- Objections and responses
- Reference Implementation
This PEP proposes a complementary syntax for Union[X,Y] and extends its purpose to isinstance and issubclass.
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  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.
Inspired by Scala language  and Pike , 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)
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  and other type checkers will need to be updated to accept this new syntax.
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..
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]
For more details about discussions, see links below:
- 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
- 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'
isinstance(x, str | int) ==> "is x an instance of str or int"
- 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
- Must migrate all the typing module in builtin
|||(1, 2) MyPy http://mypy-lang.org/|
|||Scala Union Types https://dotty.epfl.ch/docs/reference/new-types/union-types.html|
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.