Trilha de aprendizado · Nível 11 · Tutorial 6

Controlar atributos com property

Ao concluir, você poderá manter uma interface de atributo enquanto calcula valores ou valida atribuições, sem expor diretamente a organização interna do objeto.

  • Nível: Intermediário
  • Duração: 16 min
  • 6 passos
Controlar atributos com property

O que você vai percorrer

  1. Calcular valores na leitura de um atributo Use @property para expor um valor calculado com a mesma sintaxe de um atributo. 3 min
  2. Separar o acesso público do armazenamento Crie um getter e um setter para controlar a leitura e a atribuição de lado sem mudar a sintaxe usada por quem acessa o objeto. 3 min
  3. Validar antes de alterar o estado Use o setter para aceitar apenas medidas positivas e manter o último estado válido quando uma atribuição falhar. 3 min
  4. Validar também na criação do objeto Reutilize o setter no inicializador para aplicar uma única regra tanto na criação quanto nas alterações posteriores. 2 min
  5. Entender os limites de somente leitura Uma propriedade sem setter bloqueia uma atribuição direta, mas não torna automaticamente a instância nem os objetos retornados por ela imutáveis. 3 min
  6. Aplicar e verificar o contrato das propriedades Integre cálculo, validação e armazenamento interno em uma classe Quadrado e confira o contrato pela interface pública. 4 min

O que você vai aprender

  • Criar uma propriedade calculada acessível como atributo.
  • Validar atribuições com um setter e preservar o estado quando uma alteração for rejeitada.
  • Evitar recursão acidental ao separar a propriedade de seu atributo de armazenamento.
  • Distinguir uma propriedade sem setter de um objeto completamente imutável.

Antes de começar

  • Distinguir atributos de instância e de classe
  • Preservar regras de validade em objetos
  • Criar decoradores com functools.wraps

Passo 1 de 6

Calcular valores na leitura de um atributo

Use @property para expor um valor calculado com a mesma sintaxe de um atributo.

Um atributo que calcula

Leitura com lógica

Uma property permite manter a leitura com ponto — quadrado.area — enquanto Python executa um método para produzir o valor.

Ao colocar @property acima de um método, você transforma esse método em uma interface de atributo. O método recebe self e retorna o valor que será lido.

Fluxo de uma propriedade calculada

A área não precisa existir como um segundo valor salvo no objeto: ela é obtida a partir do lado atual sempre que for consultada.

Diagrama mostrando um objeto Quadrado com lado 4, uma seta para o cálculo lado vezes lado e o resultado área 16.

Ler area aciona o cálculo usando o valor atual de lado.

Defina a área calculada

Classe Quadrado

O método area é escrito como um método comum, mas @property faz com que ele seja acessado sem parênteses.

python
class Quadrado:
    def __init__(self, lado):
        self.lado = lado

    @property
    def area(self):
        return self.lado * self.lado


quadrado = Quadrado(4)
print(quadrado.area)  # 16

Exemplo

A sintaxe pública continua simples

Use quadrado.area, e não quadrado.area(). Embora area execute código internamente, para quem usa o objeto ela se comporta como uma leitura de atributo.

Assim, código que apenas consulta a área não precisa saber se ela está armazenada ou calculada.

O cálculo acompanha o lado

Sem valor derivado desatualizado

O getter de area é executado a cada leitura. Ele não guarda automaticamente o resultado: consulta self.lado e calcula de novo.

Isso evita manter uma área separada que poderia ficar desatualizada depois de uma mudança no lado.

Observe duas leituras

Execute este código no seu computador e compare as duas saídas.

python
quadrado = Quadrado(4)
print(quadrado.area)  # 16

quadrado.lado = 7
print(quadrado.area)  # 49

Verifique a consequência

Preveja a leitura

Com a classe Quadrado mostrada, o que print(quadrado.area) exibe depois de quadrado.lado = 7?

Passo 2 de 6

Separar o acesso público do armazenamento

Crie um getter e um setter para controlar a leitura e a atribuição de lado sem mudar a sintaxe usada por quem acessa o objeto.

Interface pública e valor interno

Dois papéis, dois nomes

A propriedade lado é a interface pública: o código continua lendo quadrado.lado e atribuindo quadrado.lado = 8.

O valor fica guardado em _lado. O getter lê _lado; o setter grava em _lado. Esse nome diferente evita que a propriedade chame a si mesma.

Caminhos de leitura e escrita

Diagrama de um objeto Quadrado: uma leitura de lado segue para um getter e chega ao armazenamento interno; uma atribuição a lado segue para um setter e chega ao mesmo armazenamento interno.

Use lado para acessar a interface e _lado para armazenar o valor da instância.

Getter e setter para lado

O decorador @lado.setter se aplica à propriedade já criada com @property. Os dois métodos usam o nome lado.

python
class Quadrado:
    def __init__(self, lado):
        self._lado = lado

    @property
    def lado(self):
        return self._lado

    @lado.setter
    def lado(self, novo_lado):
        self._lado = novo_lado


quadrado = Quadrado(4)
print(quadrado.lado)  # 4

quadrado.lado = 8
print(quadrado.lado)  # 8

Não chame a própria propriedade

A origem da recursão acidental

Dentro do getter, self.lado tenta ler a propriedade novamente, que executa o getter outra vez. Dentro do setter, self.lado = novo_lado tenta atribuir à mesma propriedade novamente, que executa o setter outra vez.

Nenhum dos dois chega a guardar ou retornar um valor: as chamadas se repetem até Python interromper com RecursionError. Use self._lado no corpo dos dois métodos.

Versão incorreta

Estes acessos parecem naturais, mas chamam a propriedade de novo.

python
class Quadrado:
    @property
    def lado(self):
        return self.lado  # chama o getter repetidamente

    @lado.setter
    def lado(self, novo_lado):
        self.lado = novo_lado  # chama o setter repetidamente

Verifique o armazenamento correto

Complete o setter

Para armazenar o novo valor sem chamar o setter outra vez, complete: self._____ = novo_lado

Explique o fluxo

Por que ocorre recursão?

Explique por que return self.lado no getter ou self.lado = novo_lado no setter causa recursão. Indique qual atributo deve ser usado no lugar.

Escreva pelo menos 80 caracteres (0/80).

Passo 3 de 6

Validar antes de alterar o estado

Use o setter para aceitar apenas medidas positivas e manter o último estado válido quando uma atribuição falhar.

Valide antes de gravar

O setter é a fronteira da atribuição

A interface pública continua sendo quadrado.lado = valor. No setter, teste o novo valor antes de alterar _lado.

Neste exemplo, uma medida de lado só é válida quando é maior que zero. Zero e números negativos devem gerar ValueError.

Dois caminhos de atribuição

A ordem das operações determina se o objeto preserva seu último estado válido.

Diagrama mostrando uma atribuição de lado positivo sendo validada e armazenada em _lado, atualizando a área calculada; e uma atribuição negativa sendo rejeitada com ValueError sem alterar o valor interno nem a área anterior.

Valor válido: valida e grava. Valor inválido: lança uma exceção antes de qualquer gravação.

Setter com validação

Validação antes da alteração

O raise interrompe o setter. Portanto, a linha que grava em _lado não é executada para valores inválidos.

python
class Quadrado:
    def __init__(self, lado):
        self._lado = lado

    @property
    def lado(self):
        return self._lado

    @lado.setter
    def lado(self, novo_lado):
        if novo_lado <= 0:
            raise ValueError("lado deve ser maior que zero")
        self._lado = novo_lado

    @property
    def area(self):
        return self._lado ** 2

Exemplo

Estado preservado após a falha

Com um quadrado já existente:

quadrado = Quadrado(4)
quadrado.lado = 6 → quadrado.lado é 6 e quadrado.area é 36.

Depois, quadrado.lado = -2 lança ValueError. Como a validação ocorreu antes de self._lado = novo_lado, o objeto continua com lado 6 e área 36.

Organize o fluxo seguro

Ordem de uma atribuição inválida

Ordene o que acontece quando o código executa quadrado.lado = 0.

  1. O setter lança ValueError.
  2. _lado e a área calculada permanecem inalterados.
  3. O setter recebe o novo valor: 0.
  4. A condição identifica que 0 não é maior que zero.

Preveja o resultado

O que permanece no objeto?

Considere quadrado = Quadrado(5). Ao executar quadrado.lado = 0, o que acontece com a exceção, com quadrado.lado e com quadrado.area?

Escreva pelo menos 30 caracteres (0/30).

Dica

Regra prática

Em setters que protegem um estado válido, valide primeiro e grave depois. Assim, uma rejeição não deixa o objeto parcialmente alterado.

Passo 4 de 6

Validar também na criação do objeto

Reutilize o setter no inicializador para aplicar uma única regra tanto na criação quanto nas alterações posteriores.

Uma regra, dois momentos

Inicialize pela interface pública

Se lado já possui um setter que valida valores, use self.lado = lado em __init__. Assim, a mesma regra vale ao criar o objeto e ao alterá-lo depois, sem copiá-la para dois lugares.

Fluxo da inicialização

O valor inicial passa pelo setter antes de chegar ao armazenamento interno.

Diagrama mostrando um valor inicial entrando em self.lado, passando por uma validação e chegando a _lado; um valor negativo é bloqueado antes de _lado.

Ao usar self.lado no inicializador, o setter controla a criação da instância.

Classe com validação centralizada

Quadrado

O inicializador delega a validação ao setter.

python
class Quadrado:
    def __init__(self, lado):
        self.lado = lado

    @property
    def lado(self):
        return self._lado

    @lado.setter
    def lado(self, valor):
        if valor <= 0:
            raise ValueError("lado deve ser maior que zero")
        self._lado = valor

    @property
    def area(self):
        return self._lado ** 2

Exemplo

Criação válida e inválida

quadrado = Quadrado(4) cria a instância com quadrado.lado == 4 e quadrado.area == 16.

Já Quadrado(0) executa o setter, recebe um ValueError e não devolve uma instância válida.

Dica

Não pule o setter

Evite self._lado = lado em __init__ quando o valor inicial precisar obedecer à mesma regra. Essa atribuição grava diretamente no armazenamento e contorna a validação.

Escolha a inicialização correta

Reutilizar a validação

Qual implementação de __init__ reutiliza o setter de lado?

Passo 5 de 6

Entender os limites de somente leitura

Uma propriedade sem setter bloqueia uma atribuição direta, mas não torna automaticamente a instância nem os objetos retornados por ela imutáveis.

Somente leitura não significa valor fixo

Getter sem setter

Quando uma propriedade tem apenas getter, você pode lê-la, mas não atribuir diretamente a ela. A tentativa de quadrado.area = 100 gera AttributeError.

Isso proíbe substituir o valor pela interface area. Não significa que o resultado calculado nunca mudará.

De onde vem a área?

A área continua sendo calculada a partir de lado. Ao alterar lado por sua interface permitida, a próxima leitura de area mostra outro resultado.

Diagrama de um objeto Quadrado: lado pode ser atualizado, area é apenas leitura e é recalculada a partir do lado.

area não recebe atribuições, mas seu resultado acompanha mudanças em lado.

Área calculada pode mudar

Execute este exemplo:

python
class Quadrado:
    def __init__(self, lado):
        self.lado = lado

    @property
    def area(self):
        return self.lado ** 2


quadrado = Quadrado(4)
print(quadrado.area)  # 16

quadrado.lado = 5
print(quadrado.area)  # 25

# quadrado.area = 100  # AttributeError: property 'area' ... has no setter

Ler uma lista não a congela

Referência acessível ainda pode ser mutada

Uma property também pode devolver uma lista. Sem setter, objeto.itens = outra_lista é proibido. Porém, se o getter devolve a própria lista interna, objeto.itens.append(...) modifica essa lista.

Há uma diferença importante: atribuir troca o valor associado ao atributo; chamar append modifica o objeto lista já acessado.

Exemplo

Atribuição e mutação são operações diferentes

class ListaDeCompras:
    def __init__(self):
        self._itens = ["arroz"]

    @property
    def itens(self):
        return self._itens


lista = ListaDeCompras()
lista.itens.append("feijão")  # permitido: modifica a lista retornada
print(lista.itens)             # ['arroz', 'feijão']

# lista.itens = []             # AttributeError: não há setter

A property controla a atribuição a itens; ela não impede, por si só, as operações disponíveis na lista retornada.

Cheque os limites

Valor calculado

Se area possui apenas getter e é calculada a partir de lado, então area pode mudar após uma atribuição válida a lado.

Lista retornada

Uma property sem setter que devolve uma lista impede automaticamente objeto.itens.append(valor).

Convenção interna não é bloqueio

O sentido de `_lado`

O prefixo _ comunica que _lado é um detalhe interno da implementação e que o uso normal deve ocorrer por lado. É uma convenção para pessoas que leem e usam a classe, não uma barreira de segurança imposta pelo Python.

Assim, quadrado._lado ainda pode ser acessado e alterado diretamente. Fazer isso pode contornar a validação do setter e deixar o objeto em um estado que a interface pública evitaria.

Atenção

Prefira a interface pública

Se a classe oferece lado para validar alterações, use quadrado.lado = novo_valor. Alterar quadrado._lado diretamente ignora a regra definida pela própria classe; o _ sinaliza exatamente esse risco.

Passo 6 de 6

Aplicar e verificar o contrato das propriedades

Integre cálculo, validação e armazenamento interno em uma classe Quadrado e confira o contrato pela interface pública.

Uma classe, duas interfaces de atributo

Contrato de Quadrado

Agora reúna o que você praticou: lado é a interface pública validada; _lado guarda o valor aceito; e area é calculada a cada leitura. Quem usa o objeto continua escrevendo quadrado.lado e quadrado.area, sem precisar conhecer essa organização interna.

Fluxo de acesso

A leitura de area calcula um resultado; a escrita em lado passa pela validação antes de alcançar _lado.

Diagrama de um objeto Quadrado: a escrita em lado passa por um setter e uma validação até _lado; a leitura de lado passa por um getter até _lado; a leitura de area usa _lado para calcular a área.

lado controla leitura e escrita; area é somente leitura e deriva do estado interno.

Monte e execute localmente

Prática no seu computador

Crie um arquivo chamado quadrado.py, copie o código completo abaixo e execute python quadrado.py no terminal. O bloco try permite observar a rejeição sem interromper as demais verificações.

quadrado.py

Classe completa e roteiro de verificações.

python
class Quadrado:
    def __init__(self, lado):
        self.lado = lado

    @property
    def lado(self):
        return self._lado

    @lado.setter
    def lado(self, valor):
        if valor <= 0:
            raise ValueError("lado deve ser maior que zero")
        self._lado = valor

    @property
    def area(self):
        return self._lado ** 2


quadrado = Quadrado(4)
print(quadrado.lado)  # 4
print(quadrado.area)  # 16

quadrado.lado = 5
print(quadrado.lado)  # 5
print(quadrado.area)  # 25

try:
    quadrado.lado = 0
except ValueError as erro:
    print(erro)  # lado deve ser maior que zero

print(quadrado.lado)  # 5: estado preservado
print(quadrado.area)  # 25

try:
    Quadrado(-2)
except ValueError as erro:
    print(erro)  # lado deve ser maior que zero

try:
    quadrado.area = 100
except AttributeError as erro:
    print(type(erro).__name__)  # AttributeError

Dica

O ponto decisivo

Quando quadrado.lado = 0 falha, a exceção ocorre antes de self._lado = valor. Por isso o objeto mantém o último lado válido: 5.

Confira o comportamento pela interface pública

Relate sua verificação

Execute o roteiro. Relate o que ocorreu em pelo menos quatro situações: criação com 4, mudança para 5, tentativa de usar 0, criação com -2 e atribuição a area. Explique por que o lado permanece 5 após a tentativa inválida.

Escreva pelo menos 120 caracteres (0/120).

Síntese: controle não é congelamento

Resumo

Critérios para usar property

Uma property preserva a sintaxe de atributo enquanto define o que acontece na leitura ou na atribuição.

  • Use um getter calculado, como area, para evitar armazenar valores derivados que podem ficar desatualizados.
  • Use setter para validar pela interface pública e só gravar no atributo interno depois de aceitar o valor.
  • Inicialize com self.lado = lado quando a regra do setter também deve valer na criação.
  • Uma property sem setter bloqueia a atribuição àquela propriedade, gerando AttributeError; isso não torna toda a instância imutável.
  • O prefixo _ comunica uso interno por convenção, não é uma barreira de segurança.

Tutorial concluído

Parabéns! Você concluiu: Controlar atributos com property

Muito bem! Você consegue usar property para controlar atributos sem expor a organização interna do objeto e sem confundir somente leitura com imutabilidade.

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