Tom Kuson

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]]

#python #typing

Reply to this post by email ↪