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

    Código Python com anotações de tipo em um notebook
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: leia anotações com segurança

    Aprenda annotationlib no Python para ler anotações como valores, strings ou referências futuras com mais segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    25/10/2026
    Ícone de compressão de arquivos ZIP
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ZipInfo.compress_level: controle a compressão ZIP

    Aprenda ZipInfo.compress_level no Python para definir níveis de compressão por arquivo e equilibrar tamanho, CPU e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    24/10/2026
    Código binário representando bytearray.resize no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    bytearray.resize: redimensione buffers com segurança

    Aprenda bytearray.resize no Python para expandir e reduzir buffers com segurança, limites e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    24/10/2026
    Código Python em notebook para filtrar valores None
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    operator.is_none: filtre None sem perder falsy

    Aprenda operator.is_none e is_not_none para filtrar None sem remover valores falsy válidos no Python.

    Ler mais

    Tempo de leitura: 4 minutos
    23/10/2026
    Código Python em um notebook para criar dataclasses dinâmicas
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    make_dataclass decorator: transforme dataclasses

    Aprenda a usar o parâmetro decorator de make_dataclass para validar, registrar e transformar dataclasses dinâmicas no Python.

    Ler mais

    Tempo de leitura: 6 minutos
    23/10/2026
    Estrutura de código e XML para canonicalização com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ElementTree canonicalize: normalize XML no Python

    Aprenda ElementTree canonicalize no Python para comparar XML, gerar hashes estáveis e controlar namespaces, espaços e comentários.

    Ler mais

    Tempo de leitura: 5 minutos
    22/10/2026