Skip to main content

Overview

Mypy provides three related features for declaring finality:
  1. Final names — Variables or attributes that should not be reassigned after initialization
  2. Final methods — Methods that should not be overridden in a subclass
  3. Final classes — Classes that should not be subclassed
All of these are only enforced by mypy during type checking. There is no runtime enforcement.

Final names

Use typing.Final to indicate that a name should not be reassigned, redefined, or overridden:
from typing import Final

RATE: Final = 3_000

class Base:
    DEFAULT_ID: Final = 0

RATE = 300  # Error: can't assign to final attribute
Base.DEFAULT_ID = 1  # Error: can't override a final attribute

Protecting attributes from override

Final attributes cannot be overridden in subclasses:
from typing import Final

class Window:
    BORDER_WIDTH: Final = 2.5

class ListView(Window):
    BORDER_WIDTH = 3  # Error: can't override a final attribute

Syntax variants

You can use Final in several ways:
ID: Final[int] = 1
# Inferred type: int
When omitting the type, mypy infers Literal[1] instead of int. This is more precise than Final[Any].

Details of using Final

Two main rules for defining final names:
1

At most one declaration

There can be at most one final declaration per module or class for a given attribute.
class A:
    x: Final = 1
    x: Final = 2  # Error: duplicate final declaration
2

Exactly one assignment

There must be exactly one assignment to a final name.
x: Final = 1
x = 2  # Error: cannot assign to final name

Initialization requirements

A final attribute declared in a class body without an initializer must be initialized in __init__:
class ImmutablePoint:
    x: Final[int]
    y: Final[int]  # Error: final attribute without an initializer
    
    def __init__(self) -> None:
        self.x = 1  # Good

Restrictions

Final can only be used as the outermost type:
x: list[Final[int]] = []  # Error!

def fun(x: Final[list[int]]) -> None:  # Error!
    ...
Final and ClassVar should not be used together. Mypy infers the scope automatically.

Final vs immutability

Final only prevents reassignment, not mutation:
x: Final = ['a', 'b']
x = ['c']  # Error: cannot assign to final name
To prevent mutation, use immutable types:
from collections.abc import Sequence

y: Final[Sequence[str]] = ['a', 'b']
y.append('x')  # Error: Sequence is immutable

z: Final = ('a', 'b')  # Tuples are immutable

Final methods

Use @final decorator to protect methods from being overridden:
from typing import final

class Base:
    @final
    def common_name(self) -> None:
        ...

class Derived(Base):
    def common_name(self) -> None:  # Error: cannot override a final method
        ...
The @final decorator works with:
  • Instance methods
  • Class methods
  • Static methods
  • Properties

With overloaded methods

For overloaded methods, add @final on the implementation:
from typing import final, overload

class Base:
    @overload
    def method(self) -> None: ...
    @overload
    def method(self, arg: int) -> int: ...
    @final
    def method(self, x=None):
        ...

Final classes

Use @final decorator on a class to indicate it should not be subclassed:
from typing import final

@final
class Leaf:
    ...

class MyLeaf(Leaf):  # Error: Leaf can't be subclassed
    ...

When to use final classes

The class wasn’t designed to be subclassed. Subclassing might not work as expected or could be error-prone.
You want to retain the freedom to change the class implementation arbitrarily in the future without breaking subclasses.
Subclassing would make code harder to understand or maintain by creating tight coupling between base and derived classes.

Invalid: Final abstract classes

An abstract class with @final decorator will generate an error:
from abc import ABCMeta, abstractmethod
from typing import final

@final
class A(metaclass=ABCMeta):  # Error: Final class has abstract attributes
    @abstractmethod
    def f(self, x: int) -> None:
        pass
Final classes cannot have abstract methods since those attributes could never be implemented.

Overriding final attributes

A final attribute can override a read-only property:
class Base:
    @property
    def ID(self) -> int:
        ...

class Derived(Base):
    ID: Final = 1  # OK - overriding a property with Final

Best practices

Module-level and class-level constants should be marked as Final to prevent accidental modification.
Use UPPER_CASE naming for final constants to make them visually distinct.
When building frameworks or libraries, use @final on methods that shouldn’t be overridden to maintain invariants.
Add comments explaining why a class or method is marked final to help future maintainers.

Build docs developers (and LLMs) love