Clarification for PEP 604: is `foo: int | None` to replace all use of `foo: Optional[int]`

I’m not a steering council member, but I am a typing.py maintainer (and a typeshed maintainer), so I think I’m in a reasonably good position to summarise the thoughts of the typing community.

I don’t think there are any plans to officially deprecate Union or Optional in the near future. That would cause a great deal of disruption, that we don’t really have the appetite for. We discussed this in a typing-sig thread in December 2021 – re-reading the thread, it seems like we might consider deprecating them in, perhaps, 3.15. But there wasn’t really a strong consensus around even that point, and it would significantly complicate the implementation of a bunch of typing internals[1].

However, we would encourage using int | None rather than Optional[int] if it’s possible for you to do so, and we state as such in the docs for the typing module.

From a typing perspective, Optional[int] is a very bad name for the feature, because it implies to the user that it expresses something completely different to the thing that it actually means to the type checker. For the user, it implies that the argument is… optional, i.e., it’s okay to not pass a value to that parameter. But to the type checker, the meaning is identical to Union[int, None]. That has a very different meaning, as there’s all sorts of not-None defaults that you could give an argument that would make the argument “optional” without implying that it’s okay to pass None as the argument:

from typing import Optional, Union

# This function has a *required* argument that could be `int` or `None`.
# It's required in that you *have* to pass in a value to the `arg` parameter,
# or the function will fail
def a(arg: Optional[int]) -> None:
    pass

# This function has a *optional* argument that could be `int` or `None`.
# You can call the function without passing in a value to the parameter,
# and it will work fine
def b(arg: Optional[int] = None) -> None:
    pass

# This function has an *optional* argument that can only be `str`.
# You can call the function without passing in a value to the parameter,
# and it will work fine
def b(arg: str = "foo") -> None:
    pass

If we were writing PEP 484 from scratch in 2023, I don’t think we would include the Optional symbol in the typing module. We would simply tell people to use Union[X, None] to express “it could be X, or it could be None”.


  1. For example, types.UnionType does not support forward references, whereas typing.Union does – this causes complications in various places. ↩︎

18 Likes