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 += 1ClassVar 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 = 100Em 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: floatA 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] = tipoPara 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.valorclassmethod 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 = 100Premium 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 += 1Quando 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 ** 2ClassVar 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 -= 1Esse 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 = 1ClassVar 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.







