Instead of Union[str, int] you can write str | int . In line with other type-hinted languages, the preferred (and more concise) way to denote an optional argument in Python 3.10 and up, is now Type | None , e.g. str | None or list | None .
Type hints help you build and maintain a cleaner architecture. The act of writing type hints forces you to think about the types in your program. While the dynamic nature of Python is one of its great assets, being conscious about relying on duck typing, overloaded methods, or multiple return types is a good thing.
Introduction to Type Hints As the code base gets larger, type hints can help to debug and prevent some dumb mistakes. If you're using an IDE like PyCharm, you'll get a warning message whenever you've used the wrong data type, provided you're using type hints.
Use Optional to indicate that an object is either one given type or None . For example: from typing import Dict, Optional, Union dict_of_users: Dict[int, Union[int,str]] = { 1: "Jerome", 2: "Lewis", 3: 32 } user_id: Optional[int] user_id = None # valid user_id = 3 # also vald user_id = "Hello" # not valid!
Optional[...]
is a shorthand notation for Union[..., None]
, telling the type checker that either an object of the specific type is required, or None
is required. ...
stands for any valid type hint, including complex compound types or a Union[]
of more types. Whenever you have a keyword argument with default value None
, you should use Optional
. (Note: If you are targeting Python 3.10 or newer, PEP 604 introduced a better syntax, see below).
So for your two examples, you have dict
and list
container types, but the default value for the a
keyword argument shows that None
is permitted too so use Optional[...]
:
from typing import Optional
def test(a: Optional[dict] = None) -> None:
#print(a) ==> {'a': 1234}
#or
#print(a) ==> None
def test(a: Optional[list] = None) -> None:
#print(a) ==> [1, 2, 3, 4, 'a', 'b']
#or
#print(a) ==> None
There is technically no difference between using Optional[]
on a Union[]
, or just adding None
to the Union[]
. So Optional[Union[str, int]]
and Union[str, int, None]
are exactly the same thing.
Personally, I'd stick with always using Optional[]
when setting the type for a keyword argument that uses = None
to set a default value, this documents the reason why None
is allowed better. Moreover, it makes it easier to move the Union[...]
part into a separate type alias, or to later remove the Optional[...]
part if an argument becomes mandatory.
For example, say you have
from typing import Optional, Union
def api_function(optional_argument: Optional[Union[str, int]] = None) -> None:
"""Frob the fooznar.
If optional_argument is given, it must be an id of the fooznar subwidget
to filter on. The id should be a string, or for backwards compatibility,
an integer is also accepted.
"""
then documentation is improved by pulling out the Union[str, int]
into a type alias:
from typing import Optional, Union
# subwidget ids used to be integers, now they are strings. Support both.
SubWidgetId = Union[str, int]
def api_function(optional_argument: Optional[SubWidgetId] = None) -> None:
"""Frob the fooznar.
If optional_argument is given, it must be an id of the fooznar subwidget
to filter on. The id should be a string, or for backwards compatibility,
an integer is also accepted.
"""
The refactor to move the Union[]
into an alias was made all the much easier because Optional[...]
was used instead of Union[str, int, None]
. The None
value is not a 'subwidget id' after all, it's not part of the value, None
is meant to flag the absence of a value.
Side note: Unless your code only has to support Python 3.9 or newer, you want to avoid using the standard library container types in type hinting, as you can't say anything about what types they must contain. So instead of dict
and list
, use typing.Dict
and typing.List
, respectively. And when only reading from a container type, you may just as well accept any immutable abstract container type; lists and tuples are Sequence
objects, while dict
is a Mapping
type:
from typing import Mapping, Optional, Sequence, Union
def test(a: Optional[Mapping[str, int]] = None) -> None:
"""accepts an optional map with string keys and integer values"""
# print(a) ==> {'a': 1234}
# or
# print(a) ==> None
def test(a: Optional[Sequence[Union[int, str]]] = None) -> None:
"""accepts an optional sequence of integers and strings
# print(a) ==> [1, 2, 3, 4, 'a', 'b']
# or
# print(a) ==> None
In Python 3.9 and up, the standard container types have all been updated to support using them in type hints, see PEP 585. But, while you now can use dict[str, int]
or list[Union[int, str]]
, you still may want to use the more expressive Mapping
and Sequence
annotations to indicate that a function won't be mutating the contents (they are treated as 'read only'), and that the functions would work with any object that works as a mapping or sequence, respectively.
Python 3.10 introduces the |
union operator into type hinting, see PEP 604. Instead of Union[str, int]
you can write str | int
. In line with other type-hinted languages, the preferred (and more concise) way to denote an optional argument in Python 3.10 and up, is now Type | None
, e.g. str | None
or list | None
.
Directly from mypy typing module docs.
While the accepted answer is the correct answer, one additional thing to note is that, in the context of kwargs
, both Optional[...]
and Union[..., None]
are redundant and unnecessary. If you're immediately setting your kwarg to None
, then both mypy
and IDEs assume the obvious and automatically treat the arg as Optional[...]
.
IDE:
mypy:
For variables and method/function return values, Optional[...]
is still necessary, however, as mypy
cannot know, in those cases, to automatically assume anything.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With