Curso de Python / Validar atributos com métodos e property
Uma propriedade, criada com property, permite aplicar regras de leitura e escrita mantendo a sintaxe de acesso a um atributo. Ao usar o mesmo setter na inicialização e nas alterações posteriores, uma classe pode rejeitar valores inválidos pelo caminho público de atribuição.
Separar a interface pública do armazenamento interno
No exemplo, amount é a interface pública, enquanto _amount guarda o valor. A leitura de item.amount chama uma função de acesso, o getter. A atribuição item.amount = valor chama a função de alteração, o setter. Quem usa o objeto mantém a aparência de um atributo, sem chamar esses acessores manualmente.
O sublinhado inicial em _amount indica uma parte interna por convenção, mas não bloqueia o acesso externo. Uma propriedade não é um mecanismo para esconder segredos nem uma barreira contra código que modifique diretamente esse armazenamento. Ela organiza o contrato normal da classe.
Na introdução a classes e instâncias, uma verificação em __init__() não impedia uma atribuição posterior inválida. Aqui, self.amount = amount dentro de __init__() também passa pelo setter, reunindo as duas situações sob a mesma regra.
Executar uma atualização válida e uma rejeitada
Salve o programa completo em properties.py, com UTF-8, numa pasta de prática. Execute python properties.py no terminal com o ambiente virtual selecionado se houver. Em macOS ou Ubuntu sem ambiente virtual, use python3 properties.py quando necessário. Não há dependências externas.
class Expense:
def __init__(self, amount):
self.amount = amount
@property
def amount(self):
return self._amount
@amount.setter
def amount(self, value):
if type(value) is not int or value < 0:
raise ValueError("amount must be a nonnegative integer")
self._amount = value
def add_fee(self, fee):
self.amount = self.amount + fee
item = Expense(8000)
item.add_fee(500)
print(item.amount)
try:
item.amount = -1
except ValueError:
print("rejected")
print(item.amount)
A saída esperada é:
8500
rejected
8500
A inicialização aceita 8000. O método add_fee(500) lê o valor atual, calcula 8500 e atribui o resultado à propriedade. A tentativa seguinte de atribuir -1 passa pelo mesmo setter e levanta ValueError. O tratador imprime rejected, “rejeitado”. A última linha confirma que o valor anterior continua sendo 8500.
A expressão type(value) is not int or value < 0 exige um inteiro comum não negativo. Pelo curto-circuito de or, um objeto de outro tipo é rejeitado antes da comparação com zero. Assim, uma string não chega a causar um erro de comparação nesse setter: ele próprio levanta ValueError para a política escolhida.
Evitar recursão e alterações antes da validação
| Expressão pública | Comportamento |
|---|---|
item.amount |
O getter devolve o valor interno. |
item.amount = 100 |
O setter valida e só depois atribui a _amount. |
item.add_fee(500) |
O método calcula um resultado e o submete ao setter. |
@property é um decorador que cria a interface de leitura. @amount.setter acrescenta a função que trata a escrita; o nome amount é mantido nas duas definições. Ao ler a propriedade no chamador, não coloque parênteses: item.amount() tentaria chamar o inteiro devolvido e causaria TypeError.
Dentro do setter, não use self.amount = value para armazenar o valor: essa atribuição chamaria o mesmo setter novamente e provocaria recursão, até um RecursionError. Guarde o resultado em self._amount depois de validar. A documentação de property, getter e setter apresenta esse padrão de acesso gerenciado.
A ordem também faz diferença: neste código, um valor inválido provoca a exceção antes de alterar _amount. Se a implementação atribuísse primeiro e validasse depois, capturar o erro não restauraria automaticamente o valor anterior. O teste precisa observar o estado depois da falha, além de verificar se a exceção ocorreu.
Testar o contrato da propriedade e o do método
- Rode o exemplo original e confirme as três linhas. No trecho
try, troque somenteitem.amount = -1poritem.amount = True. A política de inteiro comum rejeita o booleano, e o valor final deve continuar em 8500. - Restaure a linha e teste
item.amount = 3.5. O resultado também deve ser rejeitado, sem alterar o estado. Faça cada variação numa execução nova para manter o mesmo estado inicial. - Restaure a atribuição original e troque somente
item.add_fee(500)poritem.add_fee(-500). O valor final após essa operação é 7500, que passa pela regra da propriedade. O nome “taxa” não faz o código proibir automaticamente números negativos. - Teste então
item.add_fee(-9000). A soma daria -1000 e o setter a rejeita. Essa chamada ocorre antes dotrydo exemplo, portanto a exceção não é tratada ali e a execução termina antes das impressões. Restaure 500 e confirme novamente a saída original.
O método controla o resultado final, mas não valida isoladamente o significado de fee. Por exemplo, True somado a um inteiro pode produzir outro inteiro válido. Se uma taxa precisar ser sempre um inteiro não negativo, acrescente essa regra ao método e teste-a separadamente. Uma propriedade válida não prova que todo argumento usado para calculá-la foi adequado.
Uma propriedade sem setter pode rejeitar a atribuição comum por aquela interface, sem tornar o objeto inteiro profundamente imutável. Da mesma forma, evitar armazenamento público não impede mudanças deliberadas em atributos internos. Escolha uma propriedade para acesso a um valor e um método explícito para uma operação distinta, especialmente quando ela envolve trabalho demorado ou efeitos externos.
Não esconda uma requisição de rede ou a leitura de um arquivo grande atrás de uma consulta que parece apenas obter um número. A sintaxe deve ajudar quem usa a classe a perceber o custo e a possibilidade de falha; a prática de exceções ajuda a definir como essas falhas chegam ao chamador.




