2019-09-20 03:47:02 -04:00
|
|
|
PEP: 604
|
2020-07-28 12:02:01 -04:00
|
|
|
Title: Allow writing union types as ``X | Y``
|
2019-09-20 03:47:02 -04:00
|
|
|
Author: Philippe PRADOS <python@prados.fr>
|
|
|
|
Sponsor: Chris Angelico <rosuav@gmail.com>
|
2020-07-28 12:02:01 -04:00
|
|
|
BDFL-Delegate: Guido van Rossum <guido@python.org>
|
2019-09-20 03:47:02 -04:00
|
|
|
Status: Draft
|
|
|
|
Type: Standards Track
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
Created: 28-Aug-2019
|
2020-07-28 12:02:01 -04:00
|
|
|
Python-Version: 3.10
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
|
2019-11-06 14:02:22 -05:00
|
|
|
Abstract
|
|
|
|
========
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
This PEP proposes overloading the ``|`` operator on types to allow
|
|
|
|
writing ``Union[X, Y]`` as ``X | Y``, and allows it to appear in
|
|
|
|
``isinstance`` and ``issubclass`` calls.
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
|
|
|
|
Motivation
|
|
|
|
==========
|
|
|
|
|
2019-11-06 14:02:22 -05:00
|
|
|
PEP 484 and PEP 526 propose a generic syntax to add typing to variables,
|
2020-07-28 12:02:01 -04:00
|
|
|
parameters and function returns. PEP 585 proposes to `expose
|
|
|
|
parameters to generics at runtime
|
|
|
|
<https://www.python.org/dev/peps/pep-0585/#parameters-to-generics-are-available-at-runtime>`_.
|
|
|
|
Mypy [1]_ accepts a syntax which looks like::
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
annotation: name_type
|
|
|
|
name_type: NAME (args)?
|
|
|
|
args: '[' paramslist ']'
|
|
|
|
paramslist: annotation (',' annotation)* [',']
|
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
- To describe a disjunction (union type), the user must use ``Union[X, Y]``.
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
The verbosity of this syntax does not help the adoption.
|
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
|
2019-09-20 03:47:02 -04:00
|
|
|
Proposal
|
|
|
|
========
|
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
Inspired by Scala [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]``. In addition to
|
|
|
|
annotations, the result of this expression would then be valid in
|
2019-11-06 14:02:22 -05:00
|
|
|
``isinstance()`` and ``issubclass()``::
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-11-06 14:02:22 -05:00
|
|
|
isinstance(5, int | str)
|
|
|
|
issubclass(bool, int | float)
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
We will also be able to write ``t | None`` or ``None | t`` instead of
|
|
|
|
``Optional[t]``::
|
|
|
|
|
|
|
|
isinstance(None, int | None)
|
|
|
|
isinstance(42, None | int)
|
|
|
|
|
|
|
|
|
2019-09-20 03:47:02 -04:00
|
|
|
Examples
|
|
|
|
========
|
|
|
|
|
|
|
|
Here are some examples of what we can do with this feature.
|
|
|
|
|
|
|
|
::
|
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
# Instead of
|
2019-09-20 03:47:02 -04:00
|
|
|
# def f(list: List[Union[int, str]], param: Optional[int]) -> Union[float, str]
|
2019-09-30 03:21:17 -04:00
|
|
|
def f(list: List[int | str], param: int | None) -> float | str:
|
2019-09-20 03:47:02 -04:00
|
|
|
pass
|
|
|
|
|
2019-11-06 14:02:22 -05:00
|
|
|
f([1, "abc"], None)
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
assert str | int == Union[str,int]
|
|
|
|
assert str | int | float == Union[str, int, float]
|
|
|
|
|
|
|
|
assert isinstance("", int | str)
|
2019-11-06 14:02:22 -05:00
|
|
|
assert issubclass(bool, int | float)
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
Once the Python language is extended, mypy [1]_ and other type checkers will
|
2019-11-06 14:02:22 -05:00
|
|
|
need to be updated to accept this new syntax.
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
|
|
|
|
Implementation
|
|
|
|
==============
|
|
|
|
|
|
|
|
A new built-in ``Union`` type must be implemented to hold the return
|
|
|
|
value of ``t1 | t2``, and it must be supported by ``isinstance()`` and
|
|
|
|
``issubclass()``. This type can be placed in the ``types`` module.
|
|
|
|
Interoperability between ``types.Union`` and ``typing.Union`` must be
|
|
|
|
provided.
|
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
|
2019-09-20 03:47:02 -04:00
|
|
|
Incompatible changes
|
|
|
|
====================
|
2019-11-06 14:02:22 -05:00
|
|
|
|
2019-09-20 03:47:02 -04:00
|
|
|
In some situations, some exceptions will not be raised as expected.
|
|
|
|
|
2019-11-06 14:02:22 -05:00
|
|
|
If a metaclass implements the ``__or__`` operator, it will override this::
|
2019-09-30 03:21:17 -04:00
|
|
|
|
|
|
|
>>> class M(type):
|
2020-07-28 12:02:01 -04:00
|
|
|
... def __or__(self, other): return "Hello"
|
2019-09-30 03:21:17 -04:00
|
|
|
...
|
2020-07-28 12:02:01 -04:00
|
|
|
>>> class C(metaclass=M): pass
|
2019-09-30 03:21:17 -04:00
|
|
|
...
|
|
|
|
>>> C | int
|
|
|
|
'Hello'
|
|
|
|
>>> int | C
|
|
|
|
typing.Union[int, __main__.C]
|
2020-07-28 12:02:01 -04:00
|
|
|
>>> Union[C, int]
|
2019-09-30 03:21:17 -04:00
|
|
|
typing.Union[__main__.C, int]
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
|
2019-11-06 14:02:22 -05:00
|
|
|
Objections and responses
|
|
|
|
========================
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
For more details about discussions, see links below:
|
|
|
|
|
2019-09-20 03:47:02 -04:00
|
|
|
- `Discussion in python-ideas <https://mail.python.org/archives/list/python-ideas@python.org/thread/FCTXGDT2NNKRJQ6CDEPWUXHVG2AAQZZY/>`_
|
2019-09-30 03:21:17 -04:00
|
|
|
- `Discussion in typing-sig <https://mail.python.org/archives/list/typing-sig@python.org/thread/D5HCB4NT4S3WSK33WI26WZSFEXCEMNHN/>`_
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
1. Add a new operator for ``Union[type1|type2]``?
|
2019-11-06 14:02:22 -05:00
|
|
|
-------------------------------------------------
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
PROS:
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
- 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``
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
CONS:
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
- 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"
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
|
2020-07-29 01:27:57 -04:00
|
|
|
2. Change only PEP 484 (Type hints) to accept the syntax ``type1 | type2`` ?
|
|
|
|
----------------------------------------------------------------------------
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
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()``).
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
>>> 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'
|
|
|
|
|
2020-07-29 01:27:57 -04:00
|
|
|
3. Extend ``isinstance()`` and ``issubclass()`` to accept ``Union`` ?
|
2019-12-02 16:23:33 -05:00
|
|
|
---------------------------------------------------------------------
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
::
|
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
isinstance(x, str | int) ==> "is x an instance of str or int"
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
PROS:
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
- 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
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
CONS:
|
2019-09-20 03:47:02 -04:00
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
- Must migrate all the ``typing`` module in ``builtin``
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
|
|
|
|
Reference Implementation
|
|
|
|
========================
|
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
- A proposed implementation for `cpython is here
|
|
|
|
<https://github.com/python/cpython/pull/21515>`_.
|
|
|
|
- A proposed implementation for `mypy is here
|
|
|
|
<https://github.com/pprados/mypy/tree/PEP604>`_.
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
==========
|
|
|
|
|
2020-07-28 12:02:01 -04:00
|
|
|
.. [1] mypy
|
2019-09-20 03:47:02 -04:00
|
|
|
http://mypy-lang.org/
|
2019-12-02 16:23:33 -05:00
|
|
|
.. [2] Scala Union Types
|
2019-09-20 03:47:02 -04:00
|
|
|
https://dotty.epfl.ch/docs/reference/new-types/union-types.html
|
2019-12-02 16:23:33 -05:00
|
|
|
.. [3] Pike
|
|
|
|
http://pike.lysator.liu.se/docs/man/chapter_3.html#3.5
|
|
|
|
|
2019-09-20 03:47:02 -04:00
|
|
|
|
|
|
|
Copyright
|
|
|
|
=========
|
|
|
|
|
|
|
|
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
|
|
|
|
|
2019-12-02 16:23:33 -05:00
|
|
|
|
2019-09-20 03:47:02 -04:00
|
|
|
..
|
|
|
|
Local Variables:
|
|
|
|
mode: indented-text
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
sentence-end-double-space: t
|
|
|
|
fill-column: 70
|
|
|
|
coding: utf-8
|
|
|
|
End:
|