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>
2020-08-12 15:50:54 -04:00
Discussions-To: typing-sig@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
2020-08-12 15:50:54 -04:00
Post-History: 28-Aug-2019, 05-Aug-2020
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
2020-08-12 15:50:54 -04:00
The verbosity of this syntax does not help with type adoption.
2019-09-20 03:47:02 -04:00
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)
2020-08-12 15:50:54 -04:00
Specification
=============
2020-07-28 12:02:01 -04:00
2020-08-12 15:50:54 -04:00
The new union syntax should be accepted for function, variable and parameter annotations.
2019-09-20 03:47:02 -04:00
2020-08-12 15:50:54 -04:00
Simplified Syntax
-----------------
2019-09-20 03:47:02 -04:00
::
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
2020-08-12 15:50:54 -04:00
# Instead of typing.List[typing.Union[str, int]]
typing.List[str | int]
list[str | int]
2019-09-20 03:47:02 -04:00
2020-08-12 15:50:54 -04:00
# Instead of typing.Dict[str, typing.Union[int, float]]
typing.Dict[str, int | float]
dict[str, int | float]
2019-09-20 03:47:02 -04:00
2020-08-12 15:50:54 -04:00
The existing `` typing.Union `` and `` | `` syntax should be equivalent.
2019-09-20 03:47:02 -04:00
2020-08-12 15:50:54 -04:00
::
2020-07-28 12:02:01 -04:00
2020-08-12 15:50:54 -04:00
int | str == typing.Union[int, str]
typing.Union[int, int] == int
int | int == int
The order of the items in the Union should not matter for equality.
::
(int | str) == (str | int)
(int | str | float) == typing.Union[str, float, int]
Optional values should be equivalentl to the new union syntax
::
None | t == typing.Optional[t]
A new Union.__repr__() method should be implemented.
::
str(int | list[str])
# int | list[str]
str(int | int)
# int
isinstance and issubclass
-------------------------
The new syntax should be accepted for calls to `` isinstance `` and `` issubclass `` as long as the Union items are valid arguments to `` isinstance `` and `` issubclass `` themselves.
::
# valid
isinstance("", int | str)
# invalid
isinstance(2, list[int]) # TypeError: isinstance() argument 2 cannot be a parameterized generic
isinstance(1, int | list[int])
# valid
issubclass(bool, int | float)
# invalid
issubclass(bool, bool | list[int])
2020-07-28 12:02:01 -04:00
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
2020-08-12 15:50:54 -04:00
1. Add a new operator for `` Union[type1, type2] `` ?
--------------------------------------------------
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
2020-08-12 15:50:54 -04:00
- This syntax can be more readable, and is similar to other languages (Scala, ...)
- At runtime, `` int|str `` might return a simple object in 3.10, rather than everything that
2019-12-02 16:23:33 -05:00
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,
2020-08-12 15:50:54 -04:00
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
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
2020-08-12 15:50:54 -04:00
- Must migrate all of the `` typing `` module in `` builtin ``
2019-09-20 03:47:02 -04:00
Reference Implementation
========================
2020-08-12 15:50:54 -04:00
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.
Once the Python language is extended, mypy [1]_ and other type checkers will
need to be updated to accept this new syntax.
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: