typing.Final — это специальный тип-аннотатор из модуля typing в Python, который указывает статическим анализаторам (таким как mypy, pyright) и разработчикам, что переменная не должна переопределяться после первого присваивания. Появился в Python 3.8 (PEP 591).

🔒 Для чего нужен typing.Final

В Python нет встроенного механизма создания «настоящих» констант на уровне языка — любую переменную технически можно переписать. typing.Final решает эту проблему на уровне статической типизации: он сообщает инструментам проверки типов, что данное имя является финальным и его нельзя переприсваивать или переопределять в подклассах.

from typing import Final

MAX_SIZE: Final = 100
MAX_SIZE = 200  # mypy выдаст ошибку: Cannot assign to final name "MAX_SIZE"

Это особенно полезно в больших кодовых базах, где случайное переопределение константы может привести к трудноуловимым багам.

📋 Основные варианты использования Final

  • Модульные константы — значения конфигурации, магические числа, строки, которые не должны меняться в процессе работы программы.
  • Атрибуты класса — поля, которые задаются один раз и не должны переопределяться в дочерних классах.
  • Локальные переменные — временные вычисленные значения, которые по смыслу являются константами внутри функции.
  • Параметры конфигурации — пути, URL, ключи API, объявленные на уровне модуля.

📊 Сравнение способов объявления констант в Python

Способ Защита от переопределения Проверка статическим анализатором Поддержка в IDE Версия Python Примечание
UPPER_CASE (соглашение) ❌ Нет ❌ Нет ⚠️ Частично Любая Только договорённость команды
typing.Final ⚠️ Только статически ✅ Да (mypy, pyright) ✅ Да 3.8+ Рекомендуемый современный способ
@property без сеттера ✅ В рантайме ✅ Да ✅ Да Любая Только для атрибутов классов
__slots__ ⚠️ Частично ⚠️ Частично ⚠️ Частично Любая Не предназначен для констант
enum.Enum ✅ В рантайме ✅ Да ✅ Да 3.4+ Для групп связанных констант
namedtuple ✅ В рантайме ✅ Да ✅ Да Любая Неизменяемая структура данных
typing.Final + ClassVar ⚠️ Только статически ✅ Да ✅ Да 3.8+ Финальный атрибут класса

🏗️ typing.Final в классах

Когда Final применяется к атрибуту класса, статический анализатор запрещает переопределять этот атрибут в дочерних классах:

from typing import Final

class Config:
    TIMEOUT: Final = 30
    HOST: Final[str] = "localhost"

class MyConfig(Config):
    TIMEOUT = 60  # Ошибка: Cannot override final attribute "TIMEOUT"

Обратите внимание: Final можно использовать как Final (без указания типа) или как Final[тип] — оба варианта корректны.

🔗 Связка Final и @final

Помимо typing.Final для переменных, в модуле typing есть декоратор @final (тоже появился в Python 3.8, PEP 591). Это разные инструменты с разным назначением:

  • typing.Final — аннотация для переменных и атрибутов, запрещает переприсваивание.
  • @typing.final — декоратор для классов и методов, запрещает наследование и переопределение методов.
from typing import final

@final
class Singleton:
    pass

class MySingleton(Singleton):  # Ошибка: Cannot inherit from final class "Singleton"
    pass

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

class Child(Base):
    def important_method(self) -> None:  # Ошибка: Cannot override final method
        ...

📋 Правила и ограничения typing.Final

  • Нельзя использовать Final в параметрах функций — это вызовет ошибку анализатора.
  • Нельзя использовать Final внутри Annotated как вложенный тип.
  • Final не делает объект неизменяемым в рантайме — список, объявленный как Final, всё равно можно изменить методами (.append(), .pop() и т.д.), запрещено лишь переприсваивание самой переменной.
  • Переменная Final должна быть инициализирована либо сразу при объявлении, либо в методе __init__ класса.
  • Один и тот же атрибут не может быть объявлен как Final в нескольких местах.

⚡ Практические примеры

from typing import Final, ClassVar

# Константы модуля
VERSION: Final = "1.0.0"
MAX_RETRIES: Final[int] = 3
BASE_URL: Final[str] = "https://api.example.com"

# В классе
class DatabaseSettings:
    DEFAULT_PORT: ClassVar[Final[int]] = 5432  # финальный атрибут класса
    
    def __init__(self, host: str) -> None:
        self.host: Final = host  # финальный атрибут экземпляра — задаётся один раз в __init__

Важно помнить: ClassVar[Final[int]] и Final[ClassVar[int]] — первый вариант правильный, второй — нет.

❓ FAQ по смежным темам

Чем отличается Final от const в других языках?
В отличие от const в C++/JavaScript или final в Java, typing.Final в Python не является конструкцией языка — это лишь подсказка для статических анализаторов. Интерпретатор Python не выдаст ошибку при переопределении Final-переменной в рантайме, ошибку выдаст только mypy/pyright/pylance.
Как сделать по-настоящему неизменяемый объект в Python?
Для этого используйте неизменяемые типы данных (tuple, frozenset, str, числа), dataclasses.dataclass(frozen=True) или реализуйте __setattr__ и __delattr__, бросающие AttributeError. typing.Final для этого не подходит.
Работает ли Final с dataclass?
Да, но с оговорками. Если поле dataclass аннотировано как Final, mypy будет считать его константой. Однако сам механизм dataclass не ограничивает изменение поля в рантайме, если только не использовать параметр frozen=True.
Можно ли использовать @final вместе с @abstractmethod?
Нет, это логически противоречиво: @abstractmethod требует реализации в подклассе, а @final запрещает переопределение. Статические анализаторы выдадут предупреждение о конфликте.
Поддерживает ли Final generic-типы?
Да, синтаксис Final[тип] полностью поддерживает любые типы, включая дженерики: Final[list[int]], Final[dict[str, Any]], Final[Optional[str]] и т.д.
Как typing.final помогает при проектировании API библиотек?
Декоратор @final на классе явно сигнализирует пользователям библиотеки, что этот класс не предназначен для наследования. Это позволяет авторам библиотеки свободно изменять внутреннюю реализацию без риска сломать код, который унаследовался от этого класса.