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

Reading the PEP and its rejected ideas with hindsight, maybe a different case could be made where a: ~X = b stands for a: X | Literal[b] = b [1]:

  • The tilde could stand for “approximately”, which is fairly common use (e.g. in pip requirements).
  • The generalisation from = None to any right-hand side, still in the sense of an is relation, might delineate more clearly the difference between the new, short syntax and the existing X | type(b).

  1. Requiring extension to non-literals? ↩︎

This is just an æsthetic preference, but, for what it’s worth: same here. I don’t want to see any hasty deprecations on Optional because I still need compatibility with older Pythons for quite a while, and I don’t want tools yelling at me until the yelling is actually actionable.

Beyond the æsthetics, though, objectively speaking, Optional is specialized jargon borrwed from other languages, whereas | None is a (shorter!) statement about the type based on generalized rules that you have to learn for lots of other reasons in Python typing. Once the functional issue (i.e.: legacy compatibility) is no longer a concern, I think (as pyupgrade apparently does) that we should consider | None to be the better way to do this since it is friendlier to those newer to typing.

8 Likes

Not sure it’s worth the effort, but I do love this as an idea :wink:

But I’m afraid I’m still of the opinion that, given that there are two semantically identical ways of spelling “an argument that is either an integer which can be omitted, or None (meaning ‘use the default’)” - Optional[int] = None and int | None = None, then I prefer the former , for the following reasons:

  1. It avoids the ugly | None = None repetition.
  2. It focuses on the “can be omitted” semantics (which is what matters to me) rather than the “can be None” semantics.

I get that, but I don’t think that this case is common enough that it’s worth confusing users by conflating the two concepts. E.g. I’ve seen and written APIs like this:

def musubi(spam: int | None = 4) -> None:
    """"Make spam musubi with `spam` cans. Pass `None` to use all the spam."""

Such APIs don’t follow the pattern of using None only as fallback. They instead acknowledge that users know that they can pass None to functions to get some special default behavior. And with typed Python, more users will know that. I think seeing just the signature of this would be extremely confusing for them:

def musubi(spam: Optional[int] = 4) -> None: ...
3 Likes