Allow using the or operator to denote unions in type annotations
Hello everyone, I'm not certain if this has been brought up before, but I felt like bringing it to the table anyway. As the title says, it would be convenient to represent a union type with the or operator. While typing extra characters is not the end of the world, having a more concise representation of unions would be helpful. It also saves a few moments of thought when giving a value a type, then realizing it should actually be a union of types. Before, you would have to arrow-key both ways and surround them with "Union[" and "]" while with this new method you can simply append "or" and then the type. While not a world-changer, it might be useful. A few clarifications for those interested: This method of declaring unions will *NOT* replace "Union[...]". Instead, it will serve as syntactic sugar for representing the same thing. The next is actually a question: do we use "*bitwise* or" or "*boolean* or" (aka "|" vs "or")? I honestly don't know which would be preferable. The latter is more readable than the other but requires 3-4 characters (including whitespace) vs just 1-3 extra. Both are shorter than the 8-9 chars for the traditional union. I'll just use both in the examples for now. Finally, there are implementation details. There are some problems that should be addressed for both run-time and static analysis annotations. For run-time, these new annotations would cause an error or unwanted result in current versions. x: List | Tuple # TypeError: unsupported operand type(s) for |: ... x: List or Tuple # evaluates to "List" because of truthiness In either case, we could override the or-dunder method to return the "old" union representation of types List | Tuple == Union[List, Tuple] (Union[List, Tuple] or Union[int, str]) == Union[List, Tuple, int, str] This should allow any code relying on run-time annotations to work as expected. Note: I am uncertain if some versions of Python do not evaluate annotation expressions, but a quick test in 3.7 seems to show they do.
var: print('This expression runs') This expression runs
I don't have much to say about how this relates to static analysis. Long story short, if this becomes adopted, static analysis tools like Mypy will simply have to adapt. It will complicate the analysis process since there are now two ways to represent unions, but that's the way it would be. Thoughts? Comments? Agreement of usefulness or blatant hatred for the suggestion? Let me know 😊 Cheers, Noah May
On Sat, Mar 14, 2020 at 12:24 AM Noah Peter May <me@noahpmay.dev> wrote:
Hello everyone,
I'm not certain if this has been brought up before, but I felt like bringing it to the table anyway.
As the title says, it would be convenient to represent a union type with the or operator.
Yep! You're not the only one to want this.
The next is actually a question: do we use "bitwise or" or "boolean or" (aka "|" vs "or")? I honestly don't know which would be preferable. The latter is more readable than the other but requires 3-4 characters (including whitespace) vs just 1-3 extra. Both are shorter than the 8-9 chars for the traditional union. I'll just use both in the examples for now.
The "or" operator is defined very thoroughly by the language and can't be used for this, so the decision is made for you already - the "|" operator is the only way to go.
In either case, we could override the or-dunder method to return the "old" union representation of types
List | Tuple == Union[List, Tuple] (Union[List, Tuple] or Union[int, str]) == Union[List, Tuple, int, str]
This should allow any code relying on run-time annotations to work as expected.
Yep, agreed. Have a look at PEP 604, which is looking at this same idea. https://www.python.org/dev/peps/pep-0604/ I'm personally in favour :) ChrisA
Support for "| None" would be great to have. +1. https://github.com/python/peps/blob/master/pep-0604.rst On Fri, Mar 13, 2020, 9:36 AM Chris Angelico <rosuav@gmail.com> wrote:
On Sat, Mar 14, 2020 at 12:24 AM Noah Peter May <me@noahpmay.dev> wrote:
Hello everyone,
I'm not certain if this has been brought up before, but I felt like
bringing it to the table anyway.
As the title says, it would be convenient to represent a union type with
the or operator.
Yep! You're not the only one to want this.
The next is actually a question: do we use "bitwise or" or "boolean or" (aka "|" vs "or")? I honestly don't know which would be preferable. The latter is more readable than the other but requires 3-4 characters (including whitespace) vs just 1-3 extra. Both are shorter than the 8-9 chars for the traditional union. I'll just use both in the examples for now.
The "or" operator is defined very thoroughly by the language and can't be used for this, so the decision is made for you already - the "|" operator is the only way to go.
In either case, we could override the or-dunder method to return the "old" union representation of types
List | Tuple == Union[List, Tuple] (Union[List, Tuple] or Union[int, str]) == Union[List, Tuple, int, str]
This should allow any code relying on run-time annotations to work as expected.
Yep, agreed.
Have a look at PEP 604, which is looking at this same idea.
https://www.python.org/dev/peps/pep-0604/
I'm personally in favour :)
ChrisA _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/WQ2TI5... Code of Conduct: http://python.org/psf/codeofconduct/
First of all, I am not so happy about typing is increasing Python runtime complexity. TypeScript is the most successful language with gradual typing. It has almost zero-cost about typing. It doesn't make JavaScript runtime complex. I hoped Python goes in same way. But Python went the different way. typing affected Python runtime complexity, application memory footprint and startup time... Anyway, I like the idea of using `|` for union. I think there are two approaches to support it. a) Add `|` to all types. b) Support it only statically (`from __future__ import annotations`). Both ideas are explained in PEP 604 already. I prefer (b) to (a) because I don't want to increase runtime behavior for static typing. But (b) seems more consistent with PEP 560. Regards,
I think we should try to get PEP 604 accepted, with the runtime option (a), since that's the way we've gone already. PEP 604 proposes the right syntax, and I think mostly the right semantics (one could quibble about whether isinstance()/issubclass() ought to accept unions). Unfortunately it does not have a reasonable reference implementation. (The mypy patch looks okay, but the CPython patch is not realistic, since it attempts to import typing.py from the C code for |.) There is another PEP, PEP 585, which is complete (in fact, we should just submit it to the SC for review!). There is also a basically complete implementation, in C. It introduces a C type types.GenericAlias (note: types, not typing!) which is used to represent generics involving builtins at runtime, e.g. after "a = list[int]", a.__origin__ points to 'list' and a.__args__ is the tuple (int,). Here's an idea for an implementation of `a | b` based on that. A union could be an instance of types.GenericAlias whose __args__ is (a, b) and whose __origin__ points to some dummy type (implemented in C) that's only used to mark it as a union.
Yeah, PEP 585 should definitely get pushed through! That would be super useful all around. Besides that, I think PEP 563 would mitigate most of the slowdown at runtime introduced by 604. The only exceptions would be explicit like assigning variables to types (int_list = List[int]) or dataclasses and the like. ---- And a sidenote on 563: wouldn't this pep allow us to use basic annotations without *having* to import typing (technically speaking)? For all PEP 563 cares, this would be valid code: var1: List[Tuple[int, str]] # List and Tuple not imported my_var: Definitely_not_a_type = 42 Maybe a complementary PEP to 563 would make it standard to use unresolved type annots without importing the typing module. Unless you have something that uses annotations at runtime, I don't see a need to import the library at all. Static analysis tools would import typing themselves to do their magic. But that is also, unfortunately, pushing more work onto them and other tools like autocomplete... On Fri, Mar 13, 2020 at 10:01 PM Guido van Rossum <guido@python.org> wrote:
I think we should try to get PEP 604 accepted, with the runtime option (a), since that's the way we've gone already.
PEP 604 proposes the right syntax, and I think mostly the right semantics (one could quibble about whether isinstance()/issubclass() ought to accept unions). Unfortunately it does not have a reasonable reference implementation. (The mypy patch looks okay, but the CPython patch is not realistic, since it attempts to import typing.py from the C code for |.)
There is another PEP, PEP 585, which is complete (in fact, we should just submit it to the SC for review!). There is also a basically complete implementation, in C. It introduces a C type types.GenericAlias (note: types, not typing!) which is used to represent generics involving builtins at runtime, e.g. after "a = list[int]", a.__origin__ points to 'list' and a.__args__ is the tuple (int,).
Here's an idea for an implementation of `a | b` based on that. A union could be an instance of types.GenericAlias whose __args__ is (a, b) and whose __origin__ points to some dummy type (implemented in C) that's only used to mark it as a union.
On Sat, Mar 14, 2020 at 1:03 PM Noah Peter May <me@noahpmay.dev> wrote:
Yeah, PEP 585 should definitely get pushed through! That would be super useful all around.
Besides that, I think PEP 563 would mitigate most of the slowdown at runtime introduced by 604. The only exceptions would be explicit like assigning variables to types (int_list = List[int]) or dataclasses and the like.
Type aliases like that are actually pretty common in large code bases (which are the primary target for static typing in general) and it's pretty important not to have to suddenly switch from list[int] to List[int] there.
----
And a sidenote on 563: wouldn't this pep allow us to use basic annotations without *having* to import typing (technically speaking)? For all PEP 563 cares, this would be valid code:
var1: List[Tuple[int, str]] # List and Tuple not imported my_var: Definitely_not_a_type = 42
AFAIK none of the existing static type checkers support that, and it would be a fairly big change to make it work -- and it would probably have to be coordinated through typeshed. I would really rather not have to do this.
Maybe a complementary PEP to 563 would make it standard to use unresolved type annots without importing the typing module. Unless you have something that uses annotations at runtime, I don't see a need to import the library at all. Static analysis tools would import typing themselves to do their magic.
It seems a simple enough idea. But unless you are a current contributor to one of the existing static type checkers I don't think you have the context to know how disruptive this would be.
But that is also, unfortunately, pushing more work onto them and other tools like autocomplete...
Right. -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
Fair enough 😊, I can't say I have contributed to Mypy nor worked on a significantly large codebase. On Sat, Mar 14, 2020 at 2:12 PM Guido van Rossum <guido@python.org> wrote:
On Sat, Mar 14, 2020 at 1:03 PM Noah Peter May <me@noahpmay.dev> wrote:
Yeah, PEP 585 should definitely get pushed through! That would be super useful all around.
Besides that, I think PEP 563 would mitigate most of the slowdown at runtime introduced by 604. The only exceptions would be explicit like assigning variables to types (int_list = List[int]) or dataclasses and the like.
Type aliases like that are actually pretty common in large code bases (which are the primary target for static typing in general) and it's pretty important not to have to suddenly switch from list[int] to List[int] there.
----
And a sidenote on 563: wouldn't this pep allow us to use basic annotations without *having* to import typing (technically speaking)? For all PEP 563 cares, this would be valid code:
var1: List[Tuple[int, str]] # List and Tuple not imported my_var: Definitely_not_a_type = 42
AFAIK none of the existing static type checkers support that, and it would be a fairly big change to make it work -- and it would probably have to be coordinated through typeshed. I would really rather not have to do this.
Maybe a complementary PEP to 563 would make it standard to use unresolved type annots without importing the typing module. Unless you have something that uses annotations at runtime, I don't see a need to import the library at all. Static analysis tools would import typing themselves to do their magic.
It seems a simple enough idea. But unless you are a current contributor to one of the existing static type checkers I don't think you have the context to know how disruptive this would be.
But that is also, unfortunately, pushing more work onto them and other tools like autocomplete...
Right.
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
On Sat, Mar 14, 2020 at 11:24 AM Inada Naoki <songofacandy@gmail.com> wrote:
[snip]
a) Add `|` to all types. b) Support it only statically (`from __future__ import annotations`).
[snip]
But (b) seems more consistent with PEP 560.
I'm sorry, I meant (a) looks more consistent with PEP 560. -- Inada Naoki <songofacandy@gmail.com>
participants (5)
-
Chris Angelico
-
Guido van Rossum
-
Inada Naoki
-
Noah Peter May
-
Wes Turner