32

In the type hint system, Optional[T] is said to be equivalent to Union[T, None]

Does this work for multiple type arguments? ie,

does Optional[T,U] break out to Union[T,U,None], or do I need to write it as Optional[Union[T,U]]

Michael0x2a
  • 58,192
  • 30
  • 175
  • 224
flakes
  • 21,558
  • 8
  • 41
  • 88
  • Looks like `Optional[int, str]` gives a TypeError. Then again, `Union[None, int, str]` takes less space than what would be more readable, namely `Optional[Union[int, str]]`. – GeeTransit Mar 19 '19 at 00:55
  • 2
    The biggest point here is that if you have a function whose argument can be one of a number of types OR nothing, you should probably be more explicit about what that type means. `Username = typing.TypeVar('Username', str); UserId = typing.TypeVar('UserId', int); UserIdentifier = typing.Union[Username, UserId]; Optional[UserIdentifier]` now that _means_ something – Adam Smith Mar 19 '19 at 01:06
  • @AdamSmith I have an experimental use-case for doing mutations on a database entry. I wanted to denote three possible states for any field in a dataclass where the dataclass represents a mutation on the database entry. Say a field `t` is of type `T`. Either `t` is of `T`, therefore present and needs to be updated, `t` is `None` and should be ignored, or `t` is some marker class `type(Removal)` and needs to deleted. My fields then looked like `foo: Optional[Union[str, Removal]]`. Is there a cleaner way to reuse this typing format? Somehow get to say `foo: Modifiable[str]` – flakes Mar 19 '19 at 02:07
  • 1
    @flakes sure! Types are just objects like any others. `Modifiable = Optional[Union[str, Removal]]` is valid. – Adam Smith Mar 19 '19 at 03:15
  • 1
    If you specifically want to be able to specify your modifiable type (e.g. `Modifiable[str]` or `Modifiable[int]`) then you can define it the same way Python internally defines `Union` or `Optional`. See https://gitlab.com/snippets/1836727 – Adam Smith Mar 19 '19 at 03:24
  • @AdamSmith Amazing! Thankyou! – flakes Mar 19 '19 at 20:46

1 Answers1

22

You may think of the typing library as a specification on how to declare certain types. If something is not defined in that specification then it's always better assume it to be an undefined behavior.

However in the particular case of Python and typing we have a kind-of-reference static type checker which is mypy. So in order to get an answer for your question, or just programmatically check types, we may use it and see if it shows any warnings.

Here's an example:

$ cat check_optional.py 
import typing
def fn(x: typing.Optional[int, str]):
    pass
$ mypy check_optional.py 
check_optional.py:3: error: Optional[...] must have exactly one type argument

So no, Optional[T, U] is not possible in terms of mypy even if there's no trouble declaring it within the typing library.

Besides from "functional programming" perspective both Optional and Union are two distinct but well-known and well defined monads. A combination of two monads (Union[T, U, None]) is another monad, which however behaves differently than Optional and thus should not be named so. In other words, Union[T, U, None] is isomorphic to (=same as) Optional[Union[T, U]], but not to a general Optional[X].

bad_coder
  • 11,289
  • 20
  • 44
  • 72
Vladimir
  • 9,913
  • 4
  • 26
  • 37
  • Thanks for the mypy tutorial, I will use it in the future! In that last comment, did you mean to write that `Union[T, U, None]` is different to the optional? – flakes Mar 19 '19 at 01:58
  • 3
    Glad to hear! On the last paragraph I meant that `Union[T, U, None]` is isomorphic (=same) to `Optional[Union[T, U]]`, but not to a general `Optional[X]`. – Vladimir Mar 19 '19 at 02:22
  • PEP 604 makes it clear that that `None | x == Union[None, x] == Optional[x]` – cowlinator Jul 06 '22 at 21:00
  • So it does. Doesn't explain the issue, though – Chiffa May 17 '23 at 19:28