Python Final Type Hint vs Final Variable: Runtime vs Type Checker
python final type hint vs final variable: Understand the difference between Python's Final type hint and the final decorator, and when each is used for constants and m...
Does x: Final = 5 prevent reassignment in Python? The short answer is no. The Final annotation is a type hint for static checkers, not a runtime constraint. This article explains the python final type hint vs final variable distinction by contrasting the Final type hint with the final decorator, and shows when each is appropriate.
What Final Type Hint Actually Does
When you write MAX_RETRIES: Final = 3, you are telling type checkers like mypy or pyright that MAX_RETRIES should never be reassigned. If you later write MAX_RETRIES = 4, the type checker will report an error. At runtime, however, Final is just an annotation. The variable behaves like any other Python variable; nothing prevents reassignment. This is the core distinction: Final is a static analysis tool, not a runtime guard.
What the final Decorator Enforces at Runtime
The final decorator from typing (or typing_extensions for older Python versions) is different. It is applied to methods and classes and enforces restrictions at runtime. If you decorate a method with @final, any attempt to override that method in a subclass raises TypeError. Similarly, decorating a class with @final prevents subclassing. This is real runtime enforcement, not just a type-checker hint.
The Difference Between a Final Variable and a Final Method
The phrase "final variable" often refers to the concept of a constant—a name that should not be rebound. Python has no built-in constant keyword, so developers rely on Final type hints to communicate intent to static checkers. A "final method" (or class) is a different concept: it restricts inheritance behavior. The two are often confused because they share the word "final" but operate at different layers. Final affects variable binding; final affects method resolution and class inheritance.
Using Final for Constants in Type-Checked Code
To define a constant that type checkers will enforce as non-reassignable, use Final in the annotation. For example:
from typing import Final MAX_CONNECTIONS: Final = 100 DEFAULT_TIMEOUT: Final = 30
If you later try to assign a new value, mypy will flag it:
MAX_CONNECTIONS = 200 # error: Cannot assign to final name "MAX_CONNECTIONS"
This works for module-level variables, class attributes, and even local variables. It is a common pattern for configuration constants that should remain stable. However, note that Final does not make the object itself immutable. If the variable points to a list, you can still mutate the list; only the binding is protected.
Using final to Prevent Overriding
When you want to prevent a method from being overridden in subclasses, decorate it with @final. Here is a minimal example:
from typing import final class Service: @final def connect(self) -> None: print("Connecting") class MockService(Service): def connect(self) -> None: # raises TypeError at runtime print("Mock connect")
Instantiating MockService raises TypeError because connect is marked final. Similarly, a class decorated with @final cannot be subclassed:
from typing import final @final class Config: pass class ExtendedConfig(Config): # TypeError: type 'Config' is not an acceptable base type pass
This runtime protection is valuable for library authors who want to guarantee that certain methods are not overridden, or that a class is not extended.
Common Misconceptions and Runtime Behavior
A frequent mistake is assuming that Final prevents reassignment at runtime. It does not. The annotation is ignored by the interpreter. Only type checkers enforce it. If you need runtime protection against reassignment, you would have to implement a custom descriptor or use a library like dataclasses with frozen=True (for instance attributes). But for simple module-level constants, Final is usually sufficient because the codebase is type-checked.
Another misconception is that final is the same as Final. They are distinct identifiers. Final is a type hint; final is a decorator. Using the wrong one will not produce the intended effect. For example, applying @Final to a method does nothing at runtime and is not a valid type hint for a method.
Choosing Between Final and final
Use Final when you want to declare that a variable should not be rebound, and you rely on static type checking to catch violations. This is typical for constants, configuration values, and named numeric literals.
Use final when you need runtime enforcement of method or class immutability. This is particularly useful in frameworks or libraries where subclasses might otherwise break assumptions made by the base implementation.
In practice, you can combine both: define a constant with Final and mark a method with @final if it must not be overridden. They address different concerns and are not interchangeable.