ClassVar no Python: separe classe e instância

Publicado em: 29/08/2026
Tempo de leitura: 7 minutos
Close-up view of a computer screen displaying code in a software development environment.

Em Python, um atributo definido no corpo de uma classe pode representar configuração compartilhada, um registro, um contador, uma constante ou apenas um valor padrão que depois será substituído em cada instância. Essa ambiguidade é tolerada em runtime, mas pode confundir analisadores, dataclasses e leitores. typing.ClassVar declara explicitamente que um atributo pertence à classe e não deve ser tratado como campo de instância.

Neste guia, você aprenderá a usar ClassVar em classes comuns e dataclasses, entender herança e sombreamento, modelar caches e registros, combinar com Final quando apropriado e evitar estado compartilhado mutável acidental.

O problema da ambiguidade

class Usuario:
    total = 0
    nome = ""

total parece um contador compartilhado, enquanto nome parece um valor por instância. Sem anotações e inicialização clara, ferramentas não conseguem distinguir a intenção com segurança.

Declarando ClassVar

from typing import ClassVar

class Usuario:
    total: ClassVar[int] = 0

    def __init__(self, nome: str) -> None:
        self.nome = nome
        type(self).total += 1

ClassVar informa que total pertence à classe. O analisador pode rejeitar tentativas de tratá-lo como atributo de instância em contextos onde isso criaria confusão.

Acesso pela classe e pela instância

print(Usuario.total)
usuario = Usuario("Ana")
print(usuario.total)

O lookup de atributos do Python permite acessar um atributo de classe pela instância. Mesmo assim, prefira Usuario.total ou type(usuario).total quando a intenção é explícita. Isso evita parecer que cada objeto possui seu próprio contador.

Atribuição pela instância cria sombreamento

usuario.total = 100

Em uma classe comum, essa atribuição pode criar um atributo total no dicionário da instância, escondendo o valor da classe para aquele objeto. O contador compartilhado continua existindo em Usuario.total. Essa é uma fonte frequente de bugs.

ClassVar ajuda o checker a sinalizar a atribuição inadequada, mas não altera automaticamente o comportamento de runtime.

ClassVar em dataclasses

from dataclasses import dataclass
from typing import ClassVar

@dataclass
class Produto:
    taxa_padrao: ClassVar[float] = 0.1
    nome: str
    preco: float

A dataclass não trata taxa_padrao como campo. Ele não aparece como parâmetro gerado do __init__, não participa de comparações como campo e não é incluído por funções que enumeram campos da dataclass.

produto = Produto("Teclado", 200.0)

Sem ClassVar, taxa_padrao poderia ser interpretado como campo de instância, alterando a assinatura gerada.

Valores padrão mutáveis em dataclasses

@dataclass
class Catalogo:
    cache: ClassVar[dict[str, object]] = {}
    nome: str = "principal"

Como cache é ClassVar, o dicionário é compartilhado por todas as instâncias. Isso pode ser intencional, mas exige sincronização, política de limpeza e testes isolados. ClassVar não torna o objeto seguro nem imutável.

Estado compartilhado mutável

Listas e dicionários de classe são compartilhados. Em servidores, workers e testes, esse estado pode sobreviver mais tempo do que o esperado.

class Registro:
    itens: ClassVar[dict[str, type]] = {}

    @classmethod
    def registrar(cls, nome: str, tipo: type) -> None:
        cls.itens[nome] = tipo

Para um registry de plugins, o compartilhamento pode ser desejado. Para dados específicos de uma requisição, não é. Considere encapsular o estado, oferecer métodos de reset e proteger concorrência.

ClassVar com classmethod

class Contador:
    valor: ClassVar[int] = 0

    @classmethod
    def incrementar(cls) -> int:
        cls.valor += 1
        return cls.valor

classmethod recebe a classe concreta, permitindo que subclasses mantenham ou compartilhem estado conforme a forma de atribuição.

Herança e valores por subclasse

class Base:
    limite: ClassVar[int] = 10

class Premium(Base):
    limite = 100

Premium define seu próprio atributo, enquanto outras subclasses herdam o valor de Base. Isso é útil para configuração polimórfica. Se redefinição não deve ser permitida, considere Final em vez de apenas ClassVar.

Atualização em cls

class Base:
    chamadas: ClassVar[int] = 0

    @classmethod
    def registrar_chamada(cls) -> None:
        cls.chamadas += 1

Quando chamada em uma subclasse, a operação pode criar um atributo próprio nessa subclasse, porque a atribuição é feita em cls. Se o contador deve ser global para toda a hierarquia, atualize explicitamente Base.chamadas ou mova o estado para outro objeto.

ClassVar e Final

ClassVar diz “este atributo pertence à classe”. Final diz “este nome não deve ser redefinido”. As intenções podem coincidir, mas a sintaxe e o suporte à combinação variam entre versões de Python e analisadores.

from typing import Final

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

Para um valor fixo em toda a hierarquia, Final no corpo da classe costuma ser suficiente. Para um valor compartilhado configurável, use ClassVar. Teste combinações avançadas no checker adotado.

ClassVar e propriedades

Uma propriedade descreve acesso de instância calculado; ClassVar descreve armazenamento ou configuração da classe. Não use ClassVar para uma propriedade comum.

class Circulo:
    pi: ClassVar[float] = 3.141592653589793

    def __init__(self, raio: float) -> None:
        self.raio = raio

    @property
    def area(self) -> float:
        return self.pi * self.raio ** 2

ClassVar e Protocol

Protocol pode exigir um atributo de classe em contratos estruturais, embora o suporte detalhado varie entre analisadores.

from typing import Protocol

class Serializavel(Protocol):
    formato: ClassVar[str]

    def serializar(self) -> bytes: ...

Esse contrato indica que implementações expõem uma configuração de classe chamada formato. Teste a compatibilidade com classes reais e com o checker do projeto.

Factories e registries

class Conversor:
    _formatos: ClassVar[dict[str, type["Conversor"]]] = {}

    def __init_subclass__(cls, *, formato: str, **kwargs) -> None:
        super().__init_subclass__(**kwargs)
        Conversor._formatos[formato] = cls

    @classmethod
    def criar(cls, formato: str) -> "Conversor":
        tipo = cls._formatos[formato]
        return tipo()

O registry pertence à família de classes, não a cada conversor. Use uma referência explícita à classe base quando o mapa deve ser único para toda a hierarquia.

Caches de classe

class Parser:
    _cache: ClassVar[dict[str, object]] = {}

    @classmethod
    def compilar(cls, expressao: str) -> object:
        if expressao not in cls._cache:
            cls._cache[expressao] = compilar(expressao)
        return cls._cache[expressao]

Considere limites de memória, invalidação, concorrência e separação por subclasse. Em muitos casos, functools.lru_cache ou um serviço de cache externo é mais seguro.

Contadores de instâncias

class Conexao:
    abertas: ClassVar[int] = 0

    def __init__(self) -> None:
        type(self).abertas += 1

    def fechar(self) -> None:
        type(self).abertas -= 1

Esse exemplo é simples, mas pode ficar incorreto com fechamento duplicado, exceções e concorrência. ClassVar descreve o local do estado, não garante a lógica.

ClassVar e slots

__slots__ controla atributos de instância. Atributos de classe continuam no objeto classe. ClassVar ajuda a documentar essa separação, mas não substitui slots e não altera layout de memória.

ClassVar sem parâmetro

configuracao: ClassVar = {}

É possível omitir o tipo interno em alguns contextos, mas anotar explicitamente melhora a análise:

configuracao: ClassVar[dict[str, str]] = {}

Não use ClassVar para defaults de instância

@dataclass
class Tarefa:
    prioridade: ClassVar[int] = 1  # não vira campo
    titulo: str = ""

Se cada tarefa deve possuir prioridade própria, remova ClassVar:

@dataclass
class Tarefa:
    titulo: str
    prioridade: int = 1

ClassVar e serialização

Serializadores baseados em campos de dataclass normalmente ignoram ClassVar. Ferramentas baseadas em vars(objeto) também não veem atributos que existam apenas na classe. Se uma configuração compartilhada precisa aparecer no JSON, inclua-a explicitamente.

Testes e isolamento

Estado de classe pode vazar entre testes. Limpe registries e caches em fixtures ou ofereça métodos dedicados:

@classmethod
def limpar_cache(cls) -> None:
    cls._cache.clear()

Evite depender da ordem de execução dos testes.

Concorrência

Uma anotação ClassVar não torna operações atômicas. Incrementos, registros e caches compartilhados podem exigir locks, filas, contextvars ou armazenamento externo, dependendo de threads e processos.

Quando preferir estado de módulo

Se o estado não pertence conceitualmente à classe, uma variável privada de módulo pode ser mais simples. Use ClassVar quando a configuração ou registry faz parte do contrato da classe e deve ser acessado ou especializado pela hierarquia.

Quando preferir composição

Registries, caches e contadores complexos podem ser objetos independentes injetados na classe. Isso melhora testes, isolamento e configuração. ClassVar é conveniente, mas não deve virar um contêiner global invisível para toda dependência.

Erros comuns

  • Usar ClassVar para um campo de dataclass: ele desaparece do construtor.
  • Atribuir pela instância: pode criar sombreamento.
  • Guardar lista ou dict compartilhado sem intenção: instâncias interferem entre si.
  • Ignorar herança: subclasses podem compartilhar ou criar cópias do estado.
  • Esperar segurança de concorrência: ClassVar é apenas anotação.
  • Usar como global disfarçado: dependências ficam difíceis de testar.

Exemplo completo: codecs registrados

from typing import ClassVar

class Codec:
    _tipos: ClassVar[dict[str, type["Codec"]]] = {}
    nome: ClassVar[str]

    def __init_subclass__(cls, **kwargs) -> None:
        super().__init_subclass__(**kwargs)
        nome = getattr(cls, "nome", None)
        if nome:
            Codec._tipos[nome] = cls

    @classmethod
    def criar(cls, nome: str) -> "Codec":
        try:
            tipo = Codec._tipos[nome]
        except KeyError as erro:
            raise ValueError(f"codec desconhecido: {nome}") from erro
        return tipo()

    def codificar(self, texto: str) -> bytes:
        raise NotImplementedError

class Utf8Codec(Codec):
    nome: ClassVar[str] = "utf-8"

    def codificar(self, texto: str) -> bytes:
        return texto.encode("utf-8")

O mapa é único para a hierarquia, e cada subclasse publica um nome de classe. Nenhum desses valores é campo de uma instância de Codec.

Boas práticas

Anote estado compartilhado explicitamente. Acesse-o pela classe. Encapsule mutações em classmethods. Documente se subclasses compartilham ou substituem o valor. Evite coleções mutáveis públicas. Crie mecanismos de limpeza para testes e trate concorrência conscientemente.

Conclusão

typing.ClassVar separa atributos de classe de campos de instância e é especialmente importante em dataclasses, registries, caches, contadores e configurações de hierarquia. Ele melhora a clareza estática, mas não altera sozinho o lookup ou a mutabilidade de Python.

A documentação oficial de ClassVar no Python detalha seu uso. Combine a anotação com encapsulamento, políticas de herança, isolamento de testes e sincronização quando o estado compartilhado for mutável.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Final no Python: proteja constantes e herança

    Aprenda Final e @final no Python para proteger constantes, atributos, métodos e classes, entendendo os limites em runtime.

    Ler mais

    Tempo de leitura: 7 minutos
    29/08/2026
    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