Trilha de aprendizado · Nível 7 · Tutorial 2

Distinguir atributos de instância e de classe

Escolher onde armazenar cada atributo e corrigir compartilhamentos acidentais de estado entre objetos.

  • Nível: Intermediário
  • Duração: 16 min
  • 6 passos
Distinguir atributos de instância e de classe

O que você vai percorrer

  1. O que pertence à classe e o que pertence ao objeto Identifique se um dado representa uma característica comum ao tipo ou o estado individual de cada objeto. 2 min
  2. De onde vem o valor consultado Acompanhe o caminho de consulta de um atributo e preveja se o valor será encontrado na instância ou na classe. 2 min
  3. Atribuir na instância pode ocultar o valor da classe Veja como uma atribuição individual cria um atributo próprio e como isso afeta consultas após a alteração do valor comum da classe. 3 min
  4. Reatribuir não é modificar uma coleção Compare mutação e reatribuição para diagnosticar listas e dicionários compartilhados acidentalmente entre instâncias. 3 min
  5. Criar uma coleção própria para cada instância Mova listas e dicionários individuais para __init__ e garanta que cada objeto receba coleções independentes. 3 min
  6. Aplicação final: corrigir e explicar o estado dos objetos Analise um compartilhamento acidental de estado, corrija a classe e confirme como consultas, mutações e atribuições afetam duas instâncias. 3 min

O que você vai aprender

  • Classificar atributos como pertencentes à classe ou a cada instância.
  • Prever o resultado de consultas e atribuições quando classe e instância possuem atributos de mesmo nome.
  • Identificar uma coleção mutável compartilhada indevidamente entre instâncias.
  • Mover dados individuais para a inicialização de cada objeto.

Antes de começar

  • Criar objetos com classes e métodos de instância
  • Controlar referências e cópias de coleções

Passo 1 de 6

O que pertence à classe e o que pertence ao objeto

Identifique se um dado representa uma característica comum ao tipo ou o estado individual de cada objeto.

Classe ou instância?

A localização expressa uma escolha

Um atributo definido diretamente no corpo da classe pertence à classe. Ele é adequado quando o valor representa uma característica intencionalmente comum ao tipo.

Já os atributos definidos com self pertencem a cada instância. Eles representam o estado individual de cada objeto.

Um formato comum e títulos individuais

Nesta modelagem, todos os cadernos têm o formato A4, mas cada objeto mantém seu próprio título.

python
class Caderno:
    formato = "A4"

    def __init__(self, titulo):
        self.titulo = titulo


estudos = Caderno("Estudos")
receitas = Caderno("Receitas")

O significado dos dados

Pergunte a quem o dado pertence

Em Caderno, formato foi declarado na classe porque representa uma característica comum a todos os cadernos desse tipo. titulo foi associado a self porque descreve cada caderno individualmente.

A decisão deve seguir o significado do dado, não apenas os valores encontrados agora.

Uma classe, dois objetos

O elemento superior representa a classe e sua característica comum. Os dois elementos inferiores representam instâncias distintas, cada uma com seu próprio título.

Diagrama com uma classe Caderno contendo um símbolo compartilhado de formato A4 e duas instâncias abaixo, cada uma com um cartão de título próprio.

O formato é comum ao tipo; o título faz parte do estado de cada caderno.

Dica

Valores iguais ainda podem ser individuais

Se dois cadernos tiverem o título "Anotações", isso não torna o título uma característica da classe. Os valores podem coincidir, mas cada título continua representando o estado de um objeto específico.

Classifique pela intenção

Associe cada situação

Relacione cada declaração ou situação à classificação correta.

Toque em um item e depois no par correspondente.

Passo 2 de 6

De onde vem o valor consultado

Acompanhe o caminho de consulta de um atributo e preveja se o valor será encontrado na instância ou na classe.

A consulta começa pela instância

Do objeto para a classe

Ao consultar objeto.atributo, Python procura primeiro esse nome na própria instância. Se não o encontrar, procura na classe.

No exemplo, cada caderno possui seu próprio titulo. Já formato está na classe e pode ser consultado por qualquer instância.

Duas origens possíveis

Observe onde cada valor está armazenado.

python
class Caderno:
    formato = "A5"

    def __init__(self, titulo):
        self.titulo = titulo


azul = Caderno("Estudos")
verde = Caderno("Receitas")

print(azul.titulo)    # Estudos: encontrado na instância azul
print(verde.titulo)   # Receitas: encontrado na instância verde
print(azul.formato)   # A5: encontrado na classe Caderno
print(verde.formato)  # A5: encontrado na classe Caderno

Caminho da consulta

Diagrama com a classe Caderno e duas instâncias. A consulta de titulo termina na própria instância, enquanto a consulta de formato segue até a classe.

A instância é consultada primeiro. Somente quando o nome não está nela, a busca segue para a classe.

Consultar pela classe

A classe não procura nas instâncias

Ao usar Caderno.formato, a consulta começa na própria classe e encontra "A5".

Já Caderno.titulo não procura o título de azul, de verde nem de qualquer outra instância. Como titulo não foi definido na classe, essa consulta causa AttributeError.

Consultas pelo nome da classe

python
print(Caderno.formato)  # A5

# Caderno.titulo causaria AttributeError,
# pois titulo pertence a cada instância.

Dica

Consultar não significa copiar

Quando azul.formato encontra formato na classe, Python apenas usa o valor disponível ali. Esse acesso não cria automaticamente uma cópia de formato dentro de azul.

Preveja a origem do valor

Qual será a saída?

Considerando o código anterior, o que será exibido por print(verde.titulo, verde.formato)?

Associe cada consulta à origem

Instância ou classe?

Associe cada expressão ao resultado e à origem do atributo.

Toque em um item e depois no par correspondente.

Passo 3 de 6

Atribuir na instância pode ocultar o valor da classe

Veja como uma atribuição individual cria um atributo próprio e como isso afeta consultas após a alteração do valor comum da classe.

Uma atribuição individual cria um valor próprio

O mesmo nome em dois lugares

Nas classes simples deste tutorial, atribuir por objeto.atributo cria ou atualiza esse atributo somente naquela instância. Se a classe já tiver um atributo com o mesmo nome, o valor individual passa a ocultá-lo nas consultas feitas por esse objeto.

A mesma regra vale para uma atribuição como self.atributo = valor dentro de um método: self indica a instância que recebe o atributo.

Um formato próprio para o primeiro caderno

A atribuição em caderno_1 não modifica a classe nem acrescenta o atributo a caderno_2.

python
class Caderno:
    formato = "A5"

    def __init__(self, titulo):
        self.titulo = titulo


caderno_1 = Caderno("Estudos")
caderno_2 = Caderno("Receitas")

caderno_1.formato = "A4"

print(Caderno.formato)    # A5
print(caderno_1.formato)  # A4
print(caderno_2.formato)  # A5

O que muda ao atribuir pela classe

Quem ainda consulta o valor comum?

Agora considere uma atribuição posterior: Caderno.formato = "Carta". Ela altera o atributo da classe.

caderno_1 continua mostrando "A4", pois já possui um atributo individual chamado formato. Já caderno_2, que não possui esse atributo próprio, passa a encontrar "Carta" na classe — mesmo tendo sido criado antes da alteração.

Classe e instâncias após as atribuições

O diagrama acompanha o estado final: a classe guarda "Carta"; o primeiro objeto guarda seu próprio "A4"; o segundo não guarda um formato individual e continua consultando a classe.

Diagrama de referências mostrando a classe Caderno com formato Carta, caderno_1 com formato próprio A4 e caderno_2 consultando o formato da classe.

Um atributo próprio oculta o de classe apenas para a instância que o recebeu.

Preveja os três resultados

Complete sem executar primeiro

Considere a sequência completa: primeiro caderno_1.formato = "A4"; depois Caderno.formato = "Carta". Complete o resultado de cada consulta.

Valor na classe

Depois das duas atribuições, Caderno.formato vale _.

Valor no primeiro objeto

Depois das duas atribuições, caderno_1.formato vale _.

Valor no segundo objeto

Depois das duas atribuições, caderno_2.formato vale _.

Passo 4 de 6

Reatribuir não é modificar uma coleção

Compare mutação e reatribuição para diagnosticar listas e dicionários compartilhados acidentalmente entre instâncias.

Uma coleção, duas instâncias

O compartilhamento acidental

Quando uma lista ou um dicionário é definido no corpo da classe, várias instâncias podem encontrar o mesmo objeto mutável. Se uma delas modificar essa coleção, a mudança também será observada pela outra.

Isso é acidental quando os dados deveriam representar o estado individual de cada objeto.

Mutação da coleção encontrada na classe

As duas instâncias encontram as mesmas coleções definidas em Caderno.

python
class Caderno:
    notas = []
    marcadores = {}


caderno_a = Caderno()
caderno_b = Caderno()

caderno_a.notas.append(8.5)
caderno_a.marcadores["revisar"] = True

print(caderno_a.notas)         # [8.5]
print(caderno_b.notas)         # [8.5]
print(caderno_a.marcadores)    # {'revisar': True}
print(caderno_b.marcadores)    # {'revisar': True}

Referências para as mesmas coleções

Nos dois casos, os caminhos iniciados pelas instâncias chegam às coleções armazenadas na classe.

Comparação em dois painéis: duas instâncias apontam para uma única coleção compartilhada; depois, uma instância aponta para uma coleção própria enquanto a outra continua ligada à coleção da classe.

À esquerda, mutar a coleção afeta o objeto compartilhado. À direita, reatribuir o atributo separa apenas uma das instâncias.

Mutar ou reatribuir?

A operação determina o efeito

caderno_a.notas.append(8.5) modifica a lista já encontrada. Isso não cria automaticamente um atributo notas em caderno_a.

Da mesma forma, caderno_a.marcadores["cor"] = "azul" modifica o dicionário encontrado. A atribuição acontece em uma chave do dicionário, não no atributo da instância.

Já caderno_a.notas = [...] e caderno_a.marcadores = {...} reatribuem os atributos em caderno_a. As novas coleções ocultam as coleções de mesmo nome existentes na classe.

Reatribuição em apenas uma instância

Compare o que cada objeto consulta depois das operações.

python
class Caderno:
    notas = []
    marcadores = {}


caderno_a = Caderno()
caderno_b = Caderno()

# Mutação: altera as coleções compartilhadas.
caderno_a.notas.append(7.0)
caderno_a.marcadores["revisar"] = True

# Reatribuição: cria associações próprias em caderno_a.
caderno_a.notas = [10.0]
caderno_a.marcadores = {"favorito": True}

print(caderno_a.notas)         # [10.0]
print(caderno_b.notas)         # [7.0]
print(caderno_a.marcadores)    # {'favorito': True}
print(caderno_b.marcadores)    # {'revisar': True}

Atenção

A posição do sinal de igual importa

Em objeto.atributo = nova_colecao, o atributo da instância é reatribuído. Em objeto.dicionario[chave] = valor, quem é modificado é o dicionário encontrado pela consulta do atributo. Essas operações não têm o mesmo efeito.

Classifique as operações

Mutação versus reatribuição

Associe cada operação ao seu efeito, considerando que notas e marcadores foram definidos na classe.

Toque em um item e depois no par correspondente.

Explique o diagnóstico

Por que o outro objeto mudou?

Analise o código. Quais são as quatro saídas? Explique por que as mudanças realizadas por caderno_a aparecem em caderno_b.

class Caderno:
    notas = []
    marcadores = {}


caderno_a = Caderno()
caderno_b = Caderno()

caderno_a.notas.append("Python")
caderno_a.marcadores["página"] = 12

print(caderno_a.notas)
print(caderno_b.notas)
print(caderno_a.marcadores)
print(caderno_b.marcadores)

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 6

Criar uma coleção própria para cada instância

Mova listas e dicionários individuais para init e garanta que cada objeto receba coleções independentes.

O lugar certo para o estado individual

Crie as coleções em cada objeto

Notas e marcadores representam o estado de um caderno específico. Por isso, devem ser atribuídos a self dentro de __init__.

Cada chamada de Caderno(...) executa __init__ novamente. Assim, os literais [] e {} criam novas coleções para aquela instância.

Classe corrigida

Somente formato, que é comum a todos os cadernos, permanece na classe.

python
class Caderno:
    formato = "A5"

    def __init__(self, titulo):
        self.titulo = titulo
        self.notas = []
        self.marcadores = {}

Dica

Critério prático

Pergunte: “Uma alteração neste dado deve aparecer nos outros objetos?” Se a resposta for não, inicialize o dado em self.

Duas inicializações, coleções diferentes

Independência na prática

Ao criar dois cadernos, cada execução de __init__ produz uma lista e um dicionário novos. Alterar as coleções de estudos não modifica as coleções de viagem.

Resultado esperado

Execute este trecho depois da definição corrigida da classe.

python
estudos = Caderno("Estudos")
viagem = Caderno("Viagem")

estudos.notas.append("Revisar classes")
estudos.marcadores["Python"] = 12

print(estudos.notas)        # ['Revisar classes']
print(viagem.notas)         # []
print(estudos.marcadores)   # {'Python': 12}
print(viagem.marcadores)    # {}
print(estudos.formato)      # A5
print(viagem.formato)       # A5

Referências depois da correção

Diagrama com a classe Caderno mantendo um único formato comum e duas instâncias apontando para listas e dicionários separados.

O formato é compartilhado pela classe; notas e marcadores pertencem separadamente a cada instância.

A armadilha de apenas copiar a referência

Atenção

Atribuir à instância não basta

A linha self.notas = Caderno.notas cria um atributo na instância, mas seu valor ainda é uma referência à lista da classe. Nenhuma lista nova é criada.

Solução que continua compartilhando

Este código parece mover os dados para a instância, mas reutiliza as coleções existentes.

python
class Caderno:
    formato = "A5"
    notas = []
    marcadores = {}

    def __init__(self, titulo):
        self.titulo = titulo
        self.notas = Caderno.notas
        self.marcadores = Caderno.marcadores

Dica

Observe o valor atribuído

Para obter independência, atribua novas coleções: self.notas = [] e self.marcadores = {}. Referenciar Caderno.notas ou Caderno.marcadores apenas reutiliza os objetos já compartilhados.

Complete a correção

Lista individual

Complete a inicialização para que cada caderno tenha sua própria lista:

self.notas = ___

Dicionário individual

Complete a inicialização para que cada caderno tenha seus próprios marcadores:

self.marcadores = ___

Nova coleção ou mesma referência?

A instrução self.notas = Caderno.notas cria uma lista independente para a instância.

Passo 6 de 6

Aplicação final: corrigir e explicar o estado dos objetos

Analise um compartilhamento acidental de estado, corrija a classe e confirme como consultas, mutações e atribuições afetam duas instâncias.

Diagnóstico do problema

Preveja antes de executar

O script abaixo define duas coleções na classe, embora elas representem dados individuais de cada caderno. Primeiro, preveja as três linhas de saída. Depois, copie o código para um arquivo no seu computador e execute-o com Python 3 para conferir.

Script com estado compartilhado

Salve, por exemplo, como cadernos.py e execute com Python 3.

python
class Caderno:
    formato = "A5"
    notas = []
    marcadores = {}

    def __init__(self, titulo):
        self.titulo = titulo


estudos = Caderno("Estudos")
viagem = Caderno("Viagem")

estudos.notas.append("Revisar classes")
estudos.marcadores["POO"] = 12

estudos.formato = "A4"
Caderno.formato = "A6"

print(estudos.notas, viagem.notas)
print(estudos.marcadores, viagem.marcadores)
print(estudos.formato, viagem.formato, Caderno.formato)

Explique a saída observada

Consulta, mutação e ocultação

Quais são as três linhas de saída? Justifique por que as coleções aparecem nos dois objetos e por que a alteração de Caderno.formato afeta viagem, mas não muda o valor consultado por estudos.

Escreva pelo menos 100 caracteres (0/100).

Corrija o armazenamento

Uma coleção nova para cada objeto

Mantenha formato na classe, pois ele representa um valor comum ao tipo. Mova notas e marcadores para init: os literais [] e {} serão avaliados novamente a cada inicialização, criando coleções diferentes para cada instância. Execute esta versão no seu computador.

Classe corrigida

Este script é completo e pode substituir a versão anterior.

python
class Caderno:
    formato = "A5"

    def __init__(self, titulo):
        self.titulo = titulo
        self.notas = []
        self.marcadores = {}


estudos = Caderno("Estudos")
viagem = Caderno("Viagem")

estudos.notas.append("Revisar classes")
estudos.marcadores["POO"] = 12

estudos.formato = "A4"
Caderno.formato = "A6"

print(estudos.notas, viagem.notas)
print(estudos.marcadores, viagem.marcadores)
print(estudos.formato, viagem.formato, Caderno.formato)

Estado após a correção

O diagrama contrasta as coleções independentes com o formato compartilhado. A instância estudos também possui um formato próprio, que oculta o valor da classe somente para ela.

Diagrama com uma classe Caderno ligada ao formato comum A6 e duas instâncias com listas e dicionários separados; estudos tem formato próprio A4, enquanto viagem consulta A6 na classe.

Após a correção, cada caderno referencia suas próprias coleções; somente o valor comum permanece na classe.

Verificação e encerramento

Relate e justifique o resultado corrigido

Depois de executar a versão corrigida, registre as três linhas exibidas. Explique por que as coleções de viagem permanecem vazias e por que estudos mostra A4 enquanto viagem e Caderno mostram A6.

Escreva pelo menos 120 caracteres (0/120).

Resumo

Critério final de decisão

Escolha o local do atributo pelo significado do dado e depois preveja como ele será consultado ou alterado.

  • Valores realmente comuns ao tipo podem permanecer como atributos de classe.
  • Estado individual, especialmente listas e dicionários mutáveis, deve ser criado para cada objeto em init.
  • Modificar uma coleção encontrada na classe afeta a mesma coleção consultada por outras instâncias.
  • Atribuir objeto.atributo cria ou atualiza um valor próprio, que oculta o atributo de classe de mesmo nome.
  • Alterar Classe.atributo afeta as instâncias que ainda consultam esse nome na classe.

Tutorial concluído

Parabéns! Você concluiu: Distinguir atributos de instância e de classe

Concluído! Agora você consegue escolher onde armazenar o estado e prever os efeitos de consultas, atribuições e mutações entre objetos.

100 XP

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso