Python 3.10+: Optional[Type] or Type | None
Asked Answered
N

5

87

Now that Python 3.10 has been released, is there any preference when indicating that a parameter or returned value might be optional, i.e., can be None. So what is preferred:

Option 1:

def f(parameter: Optional[int]) -> Optional[str]:

Option 2:

def f(parameter: int | None) -> str | None:

Also, is there any preference between Type | None and None | Type?

Noami answered 4/10, 2021 at 18:4 Comment(3)
The 3.10 docs don't deprecate Union or Optional in favour of the new syntax, so it's up to you.Neoplasticism
When in doubt, choose the shorter.Ethelind
Optional was invented to indicate "type or None". It's intrinsic meaning is "type or None", so following good basic principles, using the type | None is more explicit. It also gets rid of an identifier. These are objective reasons for going with the new syntax as long as you're sure you've got the Python version to support it.Sharie
G
93

PEP 604 covers these topics in the specification section.

The existing typing.Union and | syntax should be equivalent.

int | str == typing.Union[int, str]

The order of the items in the Union should not matter for equality.

(int | str) == (str | int)
(int | str | float) == typing.Union[str, float, int]

Optional values should be equivalent to the new union syntax

None | t == typing.Optional[t]

As @jonrsharpe comments, Union and Optional are not deprecated, so the Union and | syntax are acceptable.


Łukasz Langa, a Python core developer, replied on a YouTube live related to the Python 3.10 release that Type | None is preferred over Optional[Type] for Python 3.10+.

enter image description here

Gall answered 4/10, 2021 at 18:15 Comment(2)
it's a very sad state of affairs if we need to fish for good practices in youtube comments :(Commotion
A more authoritative discussion is e.g. here on discuss.python.org.Candiecandied
C
27

I would personally go with Option 2 moving forward.

Also, just wanted to add this for awareness, but Python 3.7+ can made to support this syntax using a __future__ import as shown below. This type checks just the same; I actually got the tip from the latest release notes from Pycharm, which I'm currently using.

from __future__ import annotations


def f(parameter: int | None) -> str | None:
    ...
Conservator answered 4/10, 2021 at 18:18 Comment(0)
A
9

This is entirely my opinion, but I think that while they are equivalent to the type checker, they express very different intentionality to the human reader. So they are not interchangeable.

If you're declaring a default value, then type it as def foo(bar: Optional[int] = None) which declares an explicit intent that you don't have to pass in an argument.

However, if you don't have a default value, it may be declared def foo(bar: int | None), which declares that this is a required argument where None is one of the valid argument value.

Most of the time, you want to do the former, so most of the time you should still use Optional[int]; but there may be situations where requiring that callers explicitly pass None may be desirable.

A secondary reason is that writing def foo(bar: int | None = None) just looks ugly and confusing with the repeated None.

Apprise answered 3/9, 2022 at 10:18 Comment(4)
Note that Optional has nothing to do with the argument being optional. The parameter bar = 12 also means that the argument bar is optional, but definitely not Optional.Poppas
Interesting style guide, expressed in second and third paragraphs. However, these two shall be accepted by the coders before first paragraph fully applies ;)Snort
I can not agree more. Using some_type | None makes the code hard to reason because the pipe operator | can be used in many ways. What is the meaning of bitwise OR a type and a None value? It is very confusing...Palaeography
Pipe operator | is an extremely common representation for "or", @PéterSzilvási, going back across many systems to the last century. It reads like butter.Sharie
C
5

Non-authoritative, but I would expect Optional when

  • there is a default value provided (probably None)
  • None would be unusual for the caller to pass

While I would expect some Union or | to be used when

  • there is not a default value and/or the default is not None
  • None is also a valid value

See related suggestions at How do I add default parameters to functions when using type hinting?

Clingstone answered 4/10, 2021 at 18:15 Comment(1)
For default-None arguments I really feel that Optional[T] is so verbose, compared to other languages which have some form of T?. As type hints are a cosmetic, I sometimes just do foo(comment: str = None), and I think functions returning None (like __init__() for example) shouldn't have a return value annotation. -> None seems sick to me. But it's not a "standard", and a linter will attack me for that.Itching
L
-1

On a specific case of returning the type of the enclosing class, you still need to use Optional even if it's python3.10+. As of python3.11.6 this is still the case.

For example:

# temp.py
class A:
    def __init__(self):
        print("init")

    def test(self) -> "A" | None:
        pass

Attempting to run temp.py would run into TypeError:

$ python3.11 temp.py
Traceback (most recent call last):
  File "/Users/vietthan/Documents/temp.py", line 1, in <module>
    class A:
  File "/Users/vietthan/Documents/temp.py", line 5, in A
    def test(self) -> "A" | None:
                      ~~~~^~~~~~
TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'

I've checked mypy, black, ruff and they all don't catch this error.

Length answered 15/1 at 16:7 Comment(2)
Type expressions should be completely quoted or not at all. -> "A | None" is the correct annotation in this case, or better yet -> Self | None.Poppas
Hi @Poppas , I think for developers who saw this SO + the one on type hint returning enclosing class, they would certainly thought that "A" | None would work, VSCode+python extension (and as mentioned, the type checkers) did not catch this. If you didn't make this comment, I wouldn't know either.Length

© 2022 - 2024 — McMap. All rights reserved.