How typing.final Prevents Subclassing in Python
The @final decorator in Python's typing
module restricts inheritance by designating classes that should not be
subclassed and methods that should not be overridden. This article
explains how @final operates, focusing on its role in
static type analysis, how static type checkers enforce its rules, and
how it differs from runtime enforcement mechanisms.
Understanding the
@final Decorator
Introduced in Python 3.8 through PEP 591, @final is a
decorator provided by the standard typing module. When
applied to a class, it declares to static analysis tools that the class
is complete and must not be used as a base class for inheritance.
from typing import final
@final
class DatabaseConnection:
def connect(self) -> None:
print("Connected.")
# Attempting to subclass DatabaseConnection
class CustomConnection(DatabaseConnection):
passStatic Enforcement vs. Runtime Behavior
The most critical aspect of @final is that it does
not stop subclassing at runtime. Python is a
dynamically typed language that prioritizes runtime flexibility, meaning
the standard interpreter will execute code containing subclasses of
@final classes without raising an exception.
Instead, @final relies on static type checkers such as
Mypy, Pyright, or IDE-integrated language servers (like PyCharm or VS
Code's Pylance). When a type checker analyzes the code above, it halts
the build or flags an error:
error: Cannot inherit from final class "DatabaseConnection" [misc]
This design allows developers to catch architectural violations early in the development lifecycle during CI/CD checks or while writing code, avoiding runtime overhead.
Preventing Method Overriding
The @final decorator can also be applied directly to
individual methods within an extensible class. This allows the class to
be inherited while ensuring specific critical methods cannot be
redefined by subclasses.
class BaseService:
@final
def authenticate(self) -> bool:
# Core authentication logic that must not change
return True
class CustomService(BaseService):
def authenticate(self) -> bool: # Static type checker error
return FalseRunning Mypy against this code produces an error indicating that
authenticate cannot override the final method defined in
BaseService.
Adding Runtime Enforcement
If strict runtime prevention is required alongside static typing
guarantees, @final can be paired with Python's
__init_subclass__ hook. This hook triggers whenever a
subclass is created, allowing you to explicitly block runtime
inheritance:
from typing import final
@final
class SecureVault:
def __init_subclass__(cls, **kwargs):
raise TypeError(f"Subclassing {cls.__name__} is not allowed")
# This will raise a TypeError immediately at runtime
class BreachVault(SecureVault):
passCombining the @final decorator with
__init_subclass__ provides full coverage: static analyzers
flag the violation before code is deployed, and the Python runtime
actively refuses to construct unauthorized subclasses during
execution.