Final no Python: proteja constantes e herança

Publicado em: 29/08/2026
Tempo de leitura: 7 minutos
A developer typing code on a laptop with a Python book beside in an office.

Python permite reatribuir nomes, sobrescrever métodos e herdar de quase qualquer classe. Essa flexibilidade é útil, mas algumas partes de uma API deveriam permanecer estáveis: constantes não devem receber outro valor, um atributo de instância deve ser definido apenas uma vez, certos métodos não devem ser sobrescritos e algumas classes não foram projetadas para herança. O módulo typing oferece Final e o decorador @final para expressar essas intenções aos analisadores estáticos.

Neste guia, você aprenderá a usar Final em módulos, classes e instâncias, distinguir Final de imutabilidade real, trabalhar com constantes, dataclasses e configurações, usar @final em métodos e classes e evitar contratos que não correspondem ao comportamento de runtime.

Final em uma constante de módulo

from typing import Final

MAX_TENTATIVAS: Final[int] = 3
URL_API: Final = "https://api.exemplo.com"

A primeira forma informa explicitamente o tipo e a intenção final. Na segunda, o analisador infere str a partir do valor. Uma reatribuição posterior deve ser reportada:

MAX_TENTATIVAS = 5  # erro de tipagem

Python ainda executa essa atribuição em runtime. Final é uma restrição estática, não uma proteção automática do interpretador.

Final não torna o objeto imutável

ROTAS: Final[list[str]] = ["/inicio"]
ROTAS.append("/ajuda")  # permitido
ROTAS = []               # deve ser rejeitado pelo checker

Final impede a reatribuição do nome, mas não congela o objeto apontado. A lista continua mutável. Para uma coleção realmente imutável, escolha uma representação adequada:

ROTAS_FIXAS: Final[tuple[str, ...]] = ("/inicio", "/ajuda")

Mesmo aqui, Final protege o nome estaticamente, enquanto a tupla fornece imutabilidade de estrutura em runtime.

Final e convenção de nomes

Escrever constantes em maiúsculas é apenas uma convenção. Final acrescenta uma regra verificável pelo analisador.

TIMEOUT_PADRAO: Final[float] = 5.0

As duas práticas podem ser usadas juntas: maiúsculas ajudam leitores, e Final ajuda ferramentas.

Atributos de classe

class Protocolo:
    VERSAO: Final[str] = "1.0"

Subclasses não devem redefinir um atributo Final:

class ProtocoloNovo(Protocolo):
    VERSAO = "2.0"  # erro estático esperado

Se versões diferentes são parte legítima do design, não use Final nesse atributo. Final deve refletir uma invariável real da hierarquia.

Atributos de instância definidos uma vez

class Sessao:
    id: Final[str]

    def __init__(self, id_: str) -> None:
        self.id = id_

O atributo pode ser inicializado no construtor, mas uma nova atribuição deve ser rejeitada:

sessao = Sessao("abc")
sessao.id = "xyz"  # erro de tipagem

Novamente, isso não impede a atribuição em runtime. Para reforçar a regra, use propriedades sem setter, dataclasses congeladas ou uma implementação personalizada de __setattr__.

Onde inicializar um atributo Final

Um atributo Final deve ser inicializado em uma posição clara e única, normalmente na declaração da classe ou dentro de __init__. Atribuições condicionais complexas podem dificultar a análise.

class Requisicao:
    token: Final[str]

    def __init__(self, token: str | None) -> None:
        if token is None:
            self.token = gerar_token()
        else:
            self.token = token

Verificadores modernos podem aceitar caminhos mutuamente exclusivos, desde que todos inicializem o atributo exatamente uma vez. Para máxima clareza, calcule primeiro e atribua depois:

valor = gerar_token() if token is None else token
self.token = valor

Final em dataclasses

from dataclasses import dataclass
from typing import Final

@dataclass
class Evento:
    id: Final[str]
    payload: dict[str, object]

O comportamento e o suporte podem variar entre analisadores, especialmente quando a dataclass gera __init__. Para imutabilidade de runtime, use @dataclass(frozen=True):

@dataclass(frozen=True)
class EventoImutavel:
    id: str
    payload: dict[str, object]

Mesmo uma dataclass congelada não torna objetos internos imutáveis. Um dicionário dentro dela ainda pode ser alterado. Use estruturas imutáveis se essa for a exigência.

Final com ClassVar

ClassVar indica que um atributo pertence à classe, não às instâncias. Final indica que não deve ser redefinido. Dependendo da versão e do analisador, combinar os dois pode ter limitações sintáticas ou semânticas. Muitas vezes, uma anotação Final diretamente no corpo da classe já comunica a intenção necessária.

Quando o atributo é configurável por subclasses, use ClassVar sem Final. Quando é fixo em toda a hierarquia, use Final e teste com o checker adotado.

Final e Literal

Final protege um nome; Literal descreve um conjunto de valores exatos.

from typing import Final, Literal

MODO_PADRAO: Final[Literal["seguro"]] = "seguro"

Essa combinação raramente é necessária para constantes simples, porque o valor já é evidente. Literal é mais útil em parâmetros e retornos, enquanto Final é útil em definições que não devem mudar. Consulte o guia de Literal no Python.

Final e NewType

from typing import NewType

UsuarioId = NewType("UsuarioId", int)
USUARIO_SISTEMA: Final[UsuarioId] = UsuarioId(1)

NewType mantém a distinção semântica do valor e Final impede que o nome seja reatribuído estaticamente.

O decorador @final em métodos

from typing import final

class Autenticador:
    @final
    def validar_assinatura(self, token: str) -> bool:
        return verificar(token)

Uma subclasse não deve sobrescrever o método:

class AutenticadorCustomizado(Autenticador):
    def validar_assinatura(self, token: str) -> bool:
        return True  # erro estático esperado

O decorador comunica que o algoritmo é parte fixa do contrato. Use-o com parcimônia: impedir extensão reduz a flexibilidade da API.

@final em classes

@final
class TokenInterno:
    def __init__(self, valor: str) -> None:
        self.valor = valor

Analisadores devem rejeitar herança:

class TokenEspecial(TokenInterno):  # erro
    pass

Isso é útil quando a classe depende de invariantes que subclasses poderiam quebrar, quando usa otimizações internas ou quando composição é a extensão recomendada.

@final não bloqueia herança em runtime

O decorador do módulo typing não impede automaticamente que o interpretador crie uma subclasse. Em versões modernas, ele pode marcar um atributo de reflexão como __final__, mas a aplicação continua dependendo de ferramentas ou de verificações próprias.

Para bloquear herança em runtime, seria necessário implementar lógica como __init_subclass__:

class ClasseFechada:
    def __init_subclass__(cls) -> None:
        raise TypeError("herança não permitida")

Use essa abordagem apenas quando a proteção de runtime for realmente necessária, pois ela altera o comportamento da linguagem.

Método template com etapas finais

Uma classe pode permitir que subclasses personalizem pontos específicos, mantendo outras etapas fechadas.

class Importador:
    def executar(self, caminho: str) -> None:
        dados = self.ler(caminho)
        dados = self.transformar(dados)
        self.salvar(dados)

    def ler(self, caminho: str) -> bytes:
        raise NotImplementedError

    def transformar(self, dados: bytes) -> bytes:
        return dados

    @final
    def salvar(self, dados: bytes) -> None:
        gravar_com_auditoria(dados)

O método salvar permanece fixo porque contém uma exigência de auditoria, enquanto leitura e transformação são pontos de extensão.

Final em Protocol

Protocol descreve comportamento estrutural. Final e @final são mais ligados a implementação e herança nominal. Aplicá-los em protocolos geralmente não oferece o mesmo valor, pois classes compatíveis não precisam herdar do Protocol. Para contratos estruturais, documente a semântica e use métodos ou propriedades apropriadas.

Final e propriedades

Uma propriedade somente leitura oferece proteção prática em runtime:

class Conta:
    def __init__(self, numero: str) -> None:
        self._numero = numero

    @property
    def numero(self) -> str:
        return self._numero

O consumidor não pode atribuir conta.numero sem um setter. Final pode ser usado no armazenamento interno para reforçar a intenção estática:

class Conta:
    _numero: Final[str]

Configurações carregadas em runtime

Uma configuração pode ser final depois da inicialização, mesmo que seu valor seja lido de ambiente ou arquivo:

class Configuracao:
    ambiente: Final[str]
    timeout: Final[float]

    def __init__(self) -> None:
        self.ambiente = ler_ambiente()
        self.timeout = ler_timeout()

Final não exige que o valor seja conhecido em tempo de compilação; exige apenas que o nome não seja reatribuído depois de inicializado.

Constantes e importações

Importar um nome Final para outro módulo não impede manipulações dinâmicas. O benefício aparece quando todos os módulos são analisados e respeitam as anotações. Evite depender de Final como mecanismo de segurança contra código externo.

Final e monkey patching

Python permite alterar atributos de módulos e classes dinamicamente. Final e @final sinalizam que isso viola o contrato, mas não desativam monkey patching. Testes que substituem dependências devem preferir pontos de injeção explícitos em vez de alterar membros finais.

Quando não usar Final

  • Quando subclasses devem configurar o atributo.
  • Quando reatribuição faz parte do ciclo de vida.
  • Quando a API pública ainda está em evolução e precisa de extensibilidade.
  • Quando você quer apenas imutabilidade do objeto; Final protege o nome.
  • Quando a equipe não executa nenhum analisador estático.

Erros comuns

  • Confundir Final com const de runtime: o interpretador ainda permite reatribuição.
  • Anotar uma lista como Final e esperar congelamento: o conteúdo continua mutável.
  • Aplicar @final a todo método: a hierarquia fica difícil de estender.
  • Marcar como Final algo configurável: o contrato contradiz o design.
  • Usar Final sem inicialização clara: verificadores podem rejeitar ou inferir incorretamente.
  • Confiar em Final para segurança: dados e permissões precisam de validação real.

Exemplo completo: cliente de API estável

from typing import Final, final

URL_PADRAO: Final[str] = "https://api.exemplo.com"

class ClienteApi:
    url: Final[str]
    _cabecalho_versao: Final[str] = "X-Api-Version"

    def __init__(self, url: str = URL_PADRAO) -> None:
        self.url = url.rstrip("/")

    def obter(self, caminho: str) -> bytes:
        resposta = self._enviar("GET", caminho)
        return resposta

    @final
    def _enviar(self, metodo: str, caminho: str) -> bytes:
        headers = {self._cabecalho_versao: "1"}
        return transporte_http(
            metodo,
            f"{self.url}/{caminho.lstrip('/')}",
            headers=headers,
        )

class ClienteComCache(ClienteApi):
    def obter(self, caminho: str) -> bytes:
        if dado := cache_buscar(caminho):
            return dado
        dado = super().obter(caminho)
        cache_salvar(caminho, dado)
        return dado

A URL de cada instância é definida uma vez, o nome do cabeçalho é fixo na hierarquia e o envio centralizado não pode ser sobrescrito. A operação de alto nível obter() permanece extensível para cache.

Testando o contrato

Inclua o checker no CI e crie arquivos de teste de tipagem para reatribuições e sobrescritas inválidas. Testes de runtime devem verificar imutabilidade real somente quando ela é implementada por tuplas, propriedades, dataclasses congeladas ou outras estruturas concretas.

Conclusão

typing.Final protege nomes e atributos contra reatribuição no contrato estático, enquanto @final impede sobrescrita e herança para analisadores. Essas ferramentas ajudam a documentar invariantes e reduzir extensões acidentais.

A documentação oficial de Final e do decorador final no Python detalha a semântica. Combine-as com estruturas imutáveis ou controles de runtime quando a regra precisar ser realmente aplicada pelo programa.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Annotated no Python: tipos com metadados

    Aprenda Annotated no Python para adicionar metadados a tipos, criar validação, schemas, unidades e integrações com frameworks.

    Ler mais

    Tempo de leitura: 7 minutos
    29/08/2026
    Laptop displaying code editor on a desk with a coffee mug beside it, suggesting a workspace or home office setting.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    NewType no Python: IDs sem misturar

    Aprenda NewType no Python para separar IDs, códigos e valores primitivos, validar fronteiras e evitar misturas sem criar classes pesadas.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Never no Python: marque código inalcançável

    Aprenda typing.Never no Python para funções que não retornam, código inalcançável e verificação exaustiva com assert_never.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Macro shot capturing detailed patterns of a python in its natural surroundings.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Concatenate no Python: altere parâmetros

    Aprenda Concatenate no Python para adicionar ou ocultar parâmetros em decoradores tipados com ParamSpec, contexto e dependências.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    A close-up view of a person's hand signing a business contract on a desk with a pen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ParamSpec no Python: preserve assinaturas

    Aprenda ParamSpec no Python para preservar assinaturas em decoradores, callbacks, wrappers async e funções de ordem superior.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    A detailed image of a reticulated python showcasing its patterned scales and intricate skin texture.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    TypeIs no Python: refine os dois ramos

    Aprenda TypeIs no Python para refinar tipos nos ramos verdadeiro e falso, comparar com TypeGuard e criar predicados seguros.

    Ler mais

    Tempo de leitura: 6 minutos
    28/08/2026