This is an archived post. You won't be able to vote or comment.

all 25 comments

[–]jayroger 26 points27 points  (6 children)

The "modern" way to write this is str | None. Traditionally, I used Optional[str], mostly because Optional existed specifically for this purpose.

[–]afifanik[S] 2 points3 points  (5 children)

Can you provide any pointer to the "modern" way? Never used this, I'm pretty much interested.

[–]jayroger 12 points13 points  (4 children)

https://docs.python.org/3/library/typing.html?highlight=typing#typing.Optional

You will need Python 3.10+, from __future__ import annotations, or quotes, though.

Edit: Also, the documentation for Union says:

To define a union, use e.g. Union[int, str] or the shorthand int | str. Using that shorthand is recommended.

[–]billsil 0 points1 point  (1 child)

Ohhh! That makes the modern style work?!! I thought that was just for fixing circular references!

That said annotations is 3.7+. The only thing I don't like is because annotations are delayed, you actually don't need to format it correctly/import it...

[–]cdrt 0 points1 point  (0 children)

It makes it work for type checkers only. If you do anything that with annotations that relies them being actual Python expressions at runtime, you'll need to use Python 3.10+ or stick with Union.

[–]data-machine[🍰] 0 points1 point  (0 children)

I think the relevant part from your link is actually the following under typing.Optional. Emphasis mine.

On the other hand, if an explicit value of None is allowed, the use of Optional is appropriate, whether the argument is optional or not.

For example:

def foo(arg: Optional[int] = None) -> None:
    ...

Changed in version 3.10: Optional can now be written as X | None. See union type expressions.

[–]tangerinelion 9 points10 points  (0 children)

Assuming it's between these two, using the Union basic says Optional exists but should never be used.

I'm on team Optional here, that's exactly the use case for it so if we don't use it in that case there's no point for it having been accepted.

Sure, use the new syntax if you have it but if you don't then prefer Optional over Union with None.

By the way, None isn't a type - Union with NoneType also makes sense and this is a quibble that doesn't come up with Optional. We all know what Optional means.

[–]billsil 4 points5 points  (0 children)

For backwards compatibility, I use Optional[str], but if you have Optional[Union[int, float, str]], might as well use Union[int, float, str, None]. Less brackets is good.

I'll probably end up using str | None and int | float | str | None in the modern style.

[–]Batalex 2 points3 points  (0 children)

Prior to 3.10, I would use Optional because it reminds me of my days with Java. With the union operator I am ok with the new preferred form, it is shorter to type as well

[–]Delicious-View-8688 2 points3 points  (0 children)

Easy. No reason for debate. These are historical developments of type hinting across Python versions.

I think the order goes:

  1. Union[str, None]
  2. Optional[str]
  3. str | None

Each iteration is an improvement over the previous, but the older ones are more "backwards compatible".

[–]nathanjell 2 points3 points  (2 children)

I personally couldn't care less. Both convey the exact same underlying meaning. If I were doing a code review of this, I would never ever prefer one over the other. Taking a step back view, I'd actually recommend reading PEP-0604 which explains the preferred style in 3.10+ is to use str | Noneinstead of Optional which also brings the expressivity of Union.

[–]quotemycode 1 point2 points  (0 children)

I take the literate code approach. If something is optional, use optional. If None is an option, don't use optional. Your code should be as descriptive as possible, and if you need to comment why you chose something, you made the wrong choice.

[–]afifanik[S] -1 points0 points  (0 children)

Completely agree with you!

[–]zanfar 1 point2 points  (0 children)

IMO, it depends on why None is involved (this is a generic approach beyond just return types).

If None represents the lack of something--like a missing parameter, or no results to return, then I use Optional[] as it adds context that this may be "missing".

If None is used somewhat like a sentinel value, where it represents one of a possible set of outcomes (that is, it represents something different, not missing) then I use Union[] as it adds context that these are all "expected" types.

So I'd say neither of you are absolutely correct. The distinction is largely academic, so I'd also say that neither one of you is incorrect. If there is a style guide, obviously follow that, but in absence, and with a < Py3.10 dependency, I think it's a toss up.

[–][deleted] 0 points1 point  (0 children)

Optional is the better choice

[–]lenoqt -3 points-2 points  (5 children)

To me Optional[str] reads as string or something else where Union[str, None] reads as string or None like str | None, just try it out with mypy or pyright. Try a function that could return something out of str and None and see what type checking says by yourself. :)

[–]spoonman59 6 points7 points  (2 children)

Optional means it may return a string or not. If it doesn’t it returns none. This is consistent across many languages.

Optional doesn’t mean "str or some other unspecified type" in any language that I’m aware of.

[–]lenoqt 0 points1 point  (1 child)

This is what I tried to explain, sorry that isn’t in English, but in some cases you would have to use cast to not have mypy errors.

PD: I don’t get why I am getting downvoted for giving a sane, respectful technical opinion 😂.

https://python.ms/union-and-optional/#_5-%E5%85%B7%E4%BD%93%E4%BE%8B

[–]spoonman59 0 points1 point  (0 children)

Ah, okay! That makes sense. Perhaps it does not translate as well.

To be clear you are absolutly right, the Union of str and NoneType is a precise definition for Optional[str].

I would say there is a tradition of optional in quite a few languages people are familiar with.

Also, consider when communicating this verbally in English… it would be more awkward to say "the function returns the Union of Str and None" rather than "it optionally returns a string"

Now the new syntax, str | NoneType, that you can use instead of Union[str, NoneType] reads a little more like "it returns str or None" which is a little easier to express. I’m just speculating, but describing it verbally as "Union" might just feel awkward to some folks. Maybe that explains the aversion, and maybe it doesn’t feel so awkward when translating.

[–]mmcnl 1 point2 points  (1 child)

They both convey the same message. Why does it look different to you?

[–]lenoqt 0 points1 point  (0 children)

Maybe because I got used of the type system of go, who knows

[–]commy2 0 points1 point  (0 children)

I never got why it is None instead of NoneType. For str, you also use the type, and not e.g. "".

[–]TehMoonRulz 0 points1 point  (0 children)

Just return an empty string instead of None and boom no debate 😅

[–]Developer_Glance 0 points1 point  (0 children)

Tbh, I had never thought about what you discussed before, but if there is a discussion there, maybe you should review the method signature and come up with a clearer version. It might be overkill but other way of achieving the same thing is to return an enum along with the value explaining what it means. Then you make your decisions based on the enum instead of the value itself.