@@ -13,7 +13,7 @@ Introduction
13
13
============
14
14
15
15
This PEP describes an extension to Python language, which aims to add a complementary
16
- syntax to write ``Union[X,Y] `` and `` Optional[X] `` easier.
16
+ syntax to write ``Union[X,Y] `` easier.
17
17
18
18
19
19
Motivation
@@ -33,45 +33,14 @@ MyPy [4]_ accepts a syntax which looks like something like this:
33
33
34
34
- To describe a disjunction, the user must use ``Union[X,Y] ``.
35
35
36
- - To describe an optional value, the user must use ``Optional[X] ``.
37
-
38
36
The verbosity of this syntax does not help the adoption.
39
37
40
38
Proposal
41
39
========
42
40
43
- Inspired by Scala language [5 ]_, this proposal adds two operators in the root ``type ``:
44
-
45
- Strong proposition
46
- ------------------
47
- Add operator ``__or__() `` in the root ``type ``.
48
-
41
+ Inspired by Scala language [5 ]_, this proposal adds operator ``__or__() `` in the root ``type ``.
49
42
With this new operator, it is possible to write ``int | str `` in place of ``Union[int,str] ``.
50
-
51
43
This proposition uses the standard meaning of the ``| `` operator.
52
-
53
- Optional proposition 1
54
- ----------------------
55
- Add operator ``__invert__() `` in the root ``type ``.
56
-
57
- With this new operator, it is possible to write ``~int `` in place of ``Optional[int] ``.
58
-
59
- This proposition uses this operator because it is present in the language and it's conform to the
60
- `usage of tilde <https://www.thecut.com/article/why-the-internet-tilde-is-our-most-perfect-tool-for-snark.html >`_
61
-
62
- So, the new syntax for annotations will be:
63
-
64
- ::
65
-
66
- annotation: ( name_type | or_type | invert_type )
67
- or_type: name_type '|' annotation
68
- invert_type: '~' annotation
69
- name_type: NAME (args)?
70
- args: '[' paramslist ']'
71
- paramslist: annotation (',' annotation)* [',']
72
-
73
- Optional proposition 2
74
- ----------------------
75
44
Then, it is possible to extend ``isinstance() `` and ``issubclass() ``
76
45
to accept this new syntax:
77
46
@@ -88,14 +57,13 @@ Here are some examples of what we can do with this feature.
88
57
89
58
# in place of
90
59
# def f(list: List[Union[int, str]], param: Optional[int]) -> Union[float, str]
91
- def f(list: List[int | str], param: ~ int) -> float | str:
60
+ def f(list: List[int | str], param: int | None ) -> float | str:
92
61
pass
93
62
94
63
f([1,"abc"],None)
95
64
96
65
assert str | int == Union[str,int]
97
66
assert str | int | float == Union[str, int, float]
98
- assert ~str == Optional[str]
99
67
100
68
assert isinstance("", int | str)
101
69
assert issubclass(int, int | str)
@@ -106,11 +74,26 @@ Incompatible changes
106
74
====================
107
75
In some situations, some exceptions will not be raised as expected.
108
76
77
+ If some metaclass overload the ``__or__ `` operator, the user must resolve the ambiguities with ``Union ``.
78
+ ::
79
+
80
+ >>> class M(type):
81
+ ... def __or__(self,other): return "Hello"
82
+ ...
83
+ >>> class C(metaclass=M):pass
84
+ ...
85
+ >>> C | int
86
+ 'Hello'
87
+ >>> int | C
88
+ typing.Union[int, __main__.C]
89
+ >>> Union[C,int]
90
+ typing.Union[__main__.C, int]
109
91
110
92
Dissenting Opinion
111
93
==================
112
94
113
95
- `
Discussion in python-ideas <
https://mail.python.org/archives/list/[email protected] /thread/FCTXGDT2NNKRJQ6CDEPWUXHVG2AAQZZY/ >`_
96
+ - `
Discussion in typing-sig <
https://mail.python.org/archives/list/[email protected] /thread/D5HCB4NT4S3WSK33WI26WZSFEXCEMNHN/ >`_
114
97
115
98
1. Add a new operator for ``Union[type1|type2] ``?
116
99
--------------------------------------------------
@@ -206,69 +189,7 @@ Use ``{int, str}`` in place of ``Union[int,str]`` ?
206
189
- PRO: big advantage of ``{int, str} `` over ``int|str ``. It doesn't require adding anything to ``type ``,
207
190
and we don't need to introduce a new lightweight builtin union type.
208
191
209
- 2. Add a new operator for ``Optional[type] `` ?
210
- ----------------------------------------------
211
-
212
- - CONS: ``foo | None `` is short and readable
213
- - CONS: ``foo | None `` it's 3 fewer characters than ``Optional[foo] ``, or 30 fewer if you include the full
214
- removal of ``from typing import Optional ``. the additional gain of ``~foo `` is only 6 characters.
215
- - PRO: help the readability, with a lot of parameters:
216
-
217
- ::
218
-
219
- def f(source: str | None, destination: str | None, param: int | None):...
220
- def f(source: ~str, destination: ~str, param: ~int):...
221
-
222
- - PRO: I'm currently working on annotating a very large codebase, and ``Optional[T] `` is so frequent that I
223
- think ``T | None `` would not be enough of an improvement.
224
- - PRO: Adding a default ``__or__ `` overload to ``type `` seems a reasonable price to pay in 3.9, and
225
- ditto for ``__invert__ ``. Type checkers can support this in older Python versions using PEP 563 or in type
226
- comments or in "forward references" (types hidden in string literals).
227
- - CONS: The ``~ `` is easy to be missed (at least by human readers) and the meaning not obvious.
228
- - PRO: Also, Python's typing system is a lot easier to grasp if you're familiar with an established modern-typed
229
- language (Swift, Scala, Haskell, F#, etc.), and they also use ``Optional[T] `` (or ``optional<T> `` or ``Maybe t ``
230
- or some other spelling of the same idea) all over be place—so often that many of them have added shortcuts
231
- like ``T? `` to make it easier to write and less intrusive to read.
232
-
233
- - if yes,
234
-
235
- Add operator ``__revert__ `` in type type to use syntax like ``~int `` ?
236
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
237
- - CONS: ``~ `` is not automatically readable
238
-
239
- - *like ``:`` to separate variable and typing. *
240
-
241
- - CONS: ``~ `` means complement, which is a completely different thing from ``|None ``. ``~int `` seems like it
242
- would actually harm comprehension instead of helping.
243
- - PRO: the slight abuse of ``~int `` meaning "maybe int" is pretty plausible (consider how "approximately equal"
244
- is written mathematically).
245
- - PRO: `Possibly relevant for tilde <https://www.thecut.com/article/why-the-internet-tilde-is-our-most-perfect-tool-for-snark.html >`_
246
- - CONS: With ``~ `` there probably won't be a confusion in that sense, but someone reading it for the first time will
247
- definitely need to look it up (which is fine i.m.o.).
248
-
249
- - *Like the first time someone reading the annotation *
250
-
251
- ::
252
-
253
- def f(a=int):...
254
- def f(a:int):...
255
-
256
- Add operator ``__add__ `` in type type to use syntax like ``+int `` ?
257
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
258
- - PRO: ``+foo `` definitely seems to say "foo, plus something else" to me much more than ``~foo ``.
259
- - CONS: ``+foo `` is less intuitive than ``~foo `` for ``Optional ``
260
-
261
- Like Kotlin, add a new ``? `` operator to use syntax like ``int? `` or ``?int `` ?
262
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
263
-
264
- - CONS: It's not compatible with IPython and Jupyter Lab ``?smth `` displays help for symbol ``smth ``
265
- - CONS: With default arguments, ``?= `` looks... not great
266
-
267
- ::
268
-
269
- def f(source: str?=def_src, destination: str?=MISSING, param: int?=1): ...
270
-
271
- 3. Extend ``isinstance() `` and ``issubclass() `` to accept ``Union `` ?
192
+ 2. Extend ``isinstance() `` and ``issubclass() `` to accept ``Union `` ?
272
193
---------------------------------------------------------------------
273
194
274
195
::
@@ -277,13 +198,12 @@ Like Kotlin, add a new ``?`` operator to use syntax like ``int?`` or ``?int`` ?
277
198
278
199
- PRO: if they were permitted, then instance checks could use an extremely clean-looking notation for "any of these":
279
200
- PRO: The implementation can use the tuple present in ``Union `` parameter, without create a new instance.
280
- - CONS: Why not accept this syntax in ``except `` ?
281
201
282
202
Reference Implementation
283
203
========================
284
204
285
205
A proposed implementation for `cpython is here
286
- <https://github.com/pprados/cpython/tree/updage_isinstance > `_.
206
+ <https://github.com/pprados/cpython/tree/update_isinstance > `_.
287
207
A proposed implementation for `mypy is here
288
208
<https://github.com/pprados/mypy/tree/add_INVERT_to_types> `_.
289
209
0 commit comments