Python Type Hints - Old and new ways to write the same types
As type hints have evolved, Python has added simpler, more succinct syntaxes. But you still need to know the older forms, since Mypy uses them when reporting types.
A union type combines several types, expressing that a value may be any one of those types. PEP 484 introduced type hints and defined
Union to express union types, allowing you to write:
from typing import Union age: Union[int, float]
Later, PEP 604 added the
| operator for union syntax. This allows you to skip an import of
Union and instead write:
age: int | float
This feature was added in Python 3.10. But you can use it from Python 3.7, if you use Mypy 0.800+ and postponed evaluations with
from __future__ import annotations.
An optional type is basically a union between a type and
None. The value is either there, or
The first way to write this is with
from typing import Union name: Union[str, None]
Since optional types are very common, the original type hint PEP 484 added a shortcut,
from typing import Optional name: Optional[str]
But since PEP 604, it’s preferable to use the neat union syntax:
name: str | None
(This may even become
str? in the future, if Hynek Schlawack inebriates the right people.)
When type hints were first introduced, they avoided disruption as much as possible. To avoid altering common types, the
typing module introduced container types for builtin types. For example, you can represent a
from typing import List years: List[int]
PEP 585 later merged that capability back onto the builtin types. This allows you to write:
This applies to maany types:
- Other builtin container types:
- The base type,
- Many abstract base classes in the
- Types from the
- Regex types from the
You can see the full list in the PEP.
These changes were added in Python 3.9. But again, you can use them from Python 3.7, if you use Mypy 0.800+ and postponed evaluations with
from __future__ import annotations.
The pyupgrade tool automatically rewrites code to use the latest Python syntax. It has support for adopting all the above new syntax, including changing imports. Check out my previous post for more details.
It’s nice to use the latest and greatest syntax when writing your code. But when Mypy reports types, it often still uses the old syntax (at least as of Mypy 0.982), so you still need to know how to read it.
Take this combined example, using
reveal_type() to report the types:
age: int | float name: str | None years: list[int] reveal_type(age) reveal_type(name) reveal_type(years)
$ mypy example.py example.py:5: note: Revealed type is "Union[builtins.int, builtins.float]" example.py:6: note: Revealed type is "Union[builtins.str, None]" example.py:7: note: Revealed type is "builtins.list[builtins.int]" Success: no issues found in 1 source file
Note the differences:
- The union types use
- The builtin types are all prefixed with
listretains its new syntax, instead of using
Mypy can also report
Optional, e.g. for this misassignment:
name: str | None = 123
$ mypy example.py example.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "Optional[str]") Found 1 error in 1 file (checked 1 source file)
So, at least for now, you need to know the old syntax to understand Mypy’s output.
I’d like to see Mypy updated to report types in the newer, shorter forms. I couldn’t find a specific issue, but I would guess there’s no update right now since Mypy supports older Python versions (when not using
__future__.annotations). Also I saw there are some lingering edge cases with the PEP 604 union syntax.
Make your development more pleasant with Boost Your Django DX.
One summary email a week, no spam, I pinky promise.
- Python Type Hints - How to Do Exhaustiveness Checking
- Python Type Hints - Mypy doesn’t allow variables to change type
Tags: mypy, python