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):
    pass

Static 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 False

Running 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):
    pass

Combining 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.