Don't parameterise typing.Final
Python has a typing.Final annotation that can be used to mark that a name
cannot be reassigned. Type-checkers will complain if a final name is reassigned.
1from typing import Final
2
3CONST: Final = "This cannot be reassigned."
4CONST = "This is a type error." # Error: cannot assign to final name.Like other type annotations in Python, typing.Final can be parameterised to
annotate the underlying type as well.
1from typing import Final
2
3PORT: Final[int] = 8080 # int (mypy 1.19)However, this isn’t necessary. The type-checker will infer the type of the name absent the parametrisation. Moreover, the type inferred by the type checker will likely be the most precise valid type for its context.
1from typing import Final
2
3PORT: Final = 8080 # Literal[8080] (mypy 1.19)This differs from other type annotations users might be familiar with, which are less precise when unparameterised (such as bare generics).
It’s only worth parameterising typing.Final if the type that would be inferred
by the type-checker in its absence is incorrect or otherwise undesirable.
1from typing import Final
2
3lst: Final = [1, 2, 3]
4
5def fn(o: list[int | None]) -> None: ...
6
7fn(lst) # Got list[int], expected list[int | None] (mypy 1.19)One solution to the problem above would be to use collections.abc.Sequence in
the function signature instead which, unlike list, is covariant. However, if
we did not want to do that, another solution would be to annotate lst as
typing.Final[list[int | None]] to prevent the type-checker from inferring the
more precise (yet undesired) type of list[int]. The type of lst would then
satisfy the type required by the function signature.
By leaving typing.Final unparameterised, we avoid an extra burden and let the
type-checker infer the best type.
Additional note
Indicating that a name is final means it should not be reassigned. It can, however, be mutated. Using immutable types (such as tuples) can thus alter the resulting type.
1from typing import Final
2
3lst: Final = [1, 2, 3] # list[int]
4tup: Final = (1, 2, 3) # tuple[Literal[1], Literal[2], Literal[3]]