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:
parent
16bc2821ee
commit
6e49cec61b
120
pep-0604.rst
120
pep-0604.rst
|
@ -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>`_.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue