Pep 0604: Remove __revert__ operator (#1183)

* Add PEP-0604 to describes an extension to Python language, which aims to add a complementary
syntax to write ``Union[X,Y]`` and ``Optional[X]`` easier.

* Add sample with metaclass with __invert__ and/or __or__

* Remove operator __revert__
This commit is contained in:
Philippe PRADOS 2019-09-30 09:21:17 +02:00 committed by Chris Angelico
parent 16bc2821ee
commit 6e49cec61b
1 changed files with 20 additions and 100 deletions

View File

@ -13,7 +13,7 @@ Introduction
============ ============
This PEP describes an extension to Python language, which aims to add a complementary This PEP describes an extension to Python language, which aims to add a complementary
syntax to write ``Union[X,Y]`` and ``Optional[X]`` easier. syntax to write ``Union[X,Y]`` easier.
Motivation Motivation
@ -33,45 +33,14 @@ MyPy [4]_ accepts a syntax which looks like something like this:
- To describe a disjunction, the user must use ``Union[X,Y]``. - To describe a disjunction, the user must use ``Union[X,Y]``.
- To describe an optional value, the user must use ``Optional[X]``.
The verbosity of this syntax does not help the adoption. The verbosity of this syntax does not help the adoption.
Proposal Proposal
======== ========
Inspired by Scala language [5]_, this proposal adds two operators in the root ``type``: Inspired by Scala language [5]_, this proposal adds operator ``__or__()`` in the root ``type``.
Strong proposition
------------------
Add operator ``__or__()`` in the root ``type``.
With this new operator, it is possible to write ``int | str`` in place of ``Union[int,str]``. With this new operator, it is possible to write ``int | str`` in place of ``Union[int,str]``.
This proposition uses the standard meaning of the ``|`` operator. This proposition uses the standard meaning of the ``|`` operator.
Optional proposition 1
----------------------
Add operator ``__invert__()`` in the root ``type``.
With this new operator, it is possible to write ``~int`` in place of ``Optional[int]``.
This proposition uses this operator because it is present in the language and it's conform to the
`usage of tilde <https://www.thecut.com/article/why-the-internet-tilde-is-our-most-perfect-tool-for-snark.html>`_
So, the new syntax for annotations will be:
::
annotation: ( name_type | or_type | invert_type )
or_type: name_type '|' annotation
invert_type: '~' annotation
name_type: NAME (args)?
args: '[' paramslist ']'
paramslist: annotation (',' annotation)* [',']
Optional proposition 2
----------------------
Then, it is possible to extend ``isinstance()`` and ``issubclass()`` Then, it is possible to extend ``isinstance()`` and ``issubclass()``
to accept this new syntax: to accept this new syntax:
@ -88,14 +57,13 @@ Here are some examples of what we can do with this feature.
# in place of # in place of
# def f(list: List[Union[int, str]], param: Optional[int]) -> Union[float, str] # def f(list: List[Union[int, str]], param: Optional[int]) -> Union[float, str]
def f(list: List[int | str], param: ~int) -> float | str: def f(list: List[int | str], param: int | None) -> float | str:
pass pass
f([1,"abc"],None) f([1,"abc"],None)
assert str | int == Union[str,int] assert str | int == Union[str,int]
assert str | int | float == Union[str, int, float] assert str | int | float == Union[str, int, float]
assert ~str == Optional[str]
assert isinstance("", int | str) assert isinstance("", int | str)
assert issubclass(int, int | str) assert issubclass(int, int | str)
@ -106,11 +74,26 @@ Incompatible changes
==================== ====================
In some situations, some exceptions will not be raised as expected. In some situations, some exceptions will not be raised as expected.
If some metaclass overload the ``__or__`` operator, the user must resolve the ambiguities with ``Union``.
::
>>> 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]
Dissenting Opinion Dissenting Opinion
================== ==================
- `Discussion in python-ideas <https://mail.python.org/archives/list/python-ideas@python.org/thread/FCTXGDT2NNKRJQ6CDEPWUXHVG2AAQZZY/>`_ - `Discussion in python-ideas <https://mail.python.org/archives/list/python-ideas@python.org/thread/FCTXGDT2NNKRJQ6CDEPWUXHVG2AAQZZY/>`_
- `Discussion in typing-sig <https://mail.python.org/archives/list/typing-sig@python.org/thread/D5HCB4NT4S3WSK33WI26WZSFEXCEMNHN/>`_
1. Add a new operator for ``Union[type1|type2]``? 1. Add a new operator for ``Union[type1|type2]``?
-------------------------------------------------- --------------------------------------------------
@ -206,69 +189,7 @@ Use ``{int, str}`` in place of ``Union[int,str]`` ?
- PRO: big advantage of ``{int, str}`` over ``int|str``. It doesn't require adding anything to ``type``, - PRO: big advantage of ``{int, str}`` over ``int|str``. It doesn't require adding anything to ``type``,
and we don't need to introduce a new lightweight builtin union type. and we don't need to introduce a new lightweight builtin union type.
2. Add a new operator for ``Optional[type]`` ? 2. Extend ``isinstance()`` and ``issubclass()`` to accept ``Union`` ?
----------------------------------------------
- CONS: ``foo | None`` is short and readable
- CONS: ``foo | None`` it's 3 fewer characters than ``Optional[foo]``, or 30 fewer if you include the full
removal of ``from typing import Optional``. the additional gain of ``~foo`` is only 6 characters.
- PRO: help the readability, with a lot of parameters:
::
def f(source: str | None, destination: str | None, param: int | None):...
def f(source: ~str, destination: ~str, param: ~int):...
- PRO: I'm currently working on annotating a very large codebase, and ``Optional[T]`` is so frequent that I
think ``T | None`` would not be enough of an improvement.
- PRO: Adding a default ``__or__`` overload to ``type`` seems a reasonable price to pay in 3.9, and
ditto for ``__invert__``. Type checkers can support this in older Python versions using PEP 563 or in type
comments or in "forward references" (types hidden in string literals).
- CONS: The ``~`` is easy to be missed (at least by human readers) and the meaning not obvious.
- PRO: Also, Python's typing system is a lot easier to grasp if you're familiar with an established modern-typed
language (Swift, Scala, Haskell, F#, etc.), and they also use ``Optional[T]`` (or ``optional<T>`` or ``Maybe t``
or some other spelling of the same idea) all over be place—so often that many of them have added shortcuts
like ``T?`` to make it easier to write and less intrusive to read.
- if yes,
Add operator ``__revert__`` in type type to use syntax like ``~int`` ?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- CONS: ``~`` is not automatically readable
- *like ``:`` to separate variable and typing.*
- CONS: ``~`` means complement, which is a completely different thing from ``|None``. ``~int`` seems like it
would actually harm comprehension instead of helping.
- PRO: the slight abuse of ``~int`` meaning "maybe int" is pretty plausible (consider how "approximately equal"
is written mathematically).
- PRO: `Possibly relevant for tilde <https://www.thecut.com/article/why-the-internet-tilde-is-our-most-perfect-tool-for-snark.html>`_
- CONS: With ``~`` there probably won't be a confusion in that sense, but someone reading it for the first time will
definitely need to look it up (which is fine i.m.o.).
- *Like the first time someone reading the annotation*
::
def f(a=int):...
def f(a:int):...
Add operator ``__add__`` in type type to use syntax like ``+int`` ?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- PRO: ``+foo`` definitely seems to say "foo, plus something else" to me much more than ``~foo``.
- CONS: ``+foo`` is less intuitive than ``~foo`` for ``Optional``
Like Kotlin, add a new ``?`` operator to use syntax like ``int?`` or ``?int`` ?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- CONS: It's not compatible with IPython and Jupyter Lab ``?smth`` displays help for symbol ``smth``
- CONS: With default arguments, ``?=`` looks... not great
::
def f(source: str?=def_src, destination: str?=MISSING, param: int?=1): ...
3. Extend ``isinstance()`` and ``issubclass()`` to accept ``Union`` ?
--------------------------------------------------------------------- ---------------------------------------------------------------------
:: ::
@ -277,13 +198,12 @@ Like Kotlin, add a new ``?`` operator to use syntax like ``int?`` or ``?int`` ?
- PRO: if they were permitted, then instance checks could use an extremely clean-looking notation for "any of these": - PRO: if they were permitted, then instance checks could use an extremely clean-looking notation for "any of these":
- PRO: The implementation can use the tuple present in ``Union`` parameter, without create a new instance. - PRO: The implementation can use the tuple present in ``Union`` parameter, without create a new instance.
- CONS: Why not accept this syntax in ``except`` ?
Reference Implementation Reference Implementation
======================== ========================
A proposed implementation for `cpython is here A proposed implementation for `cpython is here
<https://github.com/pprados/cpython/tree/updage_isinstance>`_. <https://github.com/pprados/cpython/tree/update_isinstance>`_.
A proposed implementation for `mypy is here A proposed implementation for `mypy is here
<https://github.com/pprados/mypy/tree/add_INVERT_to_types>`_. <https://github.com/pprados/mypy/tree/add_INVERT_to_types>`_.