PEP: 604 Title: Complementary syntax for ``Union[]`` Author: Philippe PRADOS Sponsor: Chris Angelico BDFL-Delegate: Ivan Levkivskyi Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 28-Aug-2019 Python-Version: 3.9 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: - `Discussion in python-ideas `_ - `Discussion in typing-sig `_ 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 "", line 1, in File "", line 1, in 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] 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. .. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 coding: utf-8 End: