Properties
Esta aula aborda o uso de properties em Python, incluindo o decorador @property, getters e setters, validação de dados e atributos computados. Você aprenderá a controlar o acesso a atributos de forma elegante e a adicionar lógica de validação sem quebrar a interface pública da classe.
Properties são um recurso poderoso do Python que permite que métodos sejam acessados como se fossem atributos simples. Elas são frequentemente usadas para implementar getters, setters e deleters de forma controlada, mantendo a interface limpa e permitindo adicionar lógica extra (como validação) sem alterar a forma como a classe é usada externamente.
Nesta aula, exploraremos o decorador @property, que transforma um método em um atributo somente leitura, e também como criar setters e deleters usando @nome.setter e @nome.deleter. Veremos como validar dados, calcular atributos dinamicamente e boas práticas para usar properties em suas classes.
@property
O decorador @property é a forma mais simples de criar uma property. Ele permite que um método seja acessado como um atributo, sem parênteses. Isso é útil quando você quer expor um valor calculado ou controlar o acesso a um atributo privado.
Por exemplo, suponha uma classe Pessoa com um atributo _nome (privado). Podemos criar uma property nome que retorna o valor de _nome. Assim, externamente, usamos pessoa.nome em vez de pessoa._nome, e ainda podemos modificar a implementação interna sem afetar o código cliente.
class Pessoa:
def __init__(self, nome):
self._nome = nome
@property
def nome(self):
return self._nome
p = Pessoa("João")
print(p.nome) # João
# p.nome = "Maria" # AttributeError: can't set attributeNote que, sem um setter, a property é somente leitura. Tentar atribuir um valor causa um AttributeError.
Getters e setters
Para permitir a modificação controlada de um atributo, usamos o decorador @nome.setter. O método setter deve ter o mesmo nome da property e receber o valor a ser atribuído. Isso nos permite executar lógica adicional, como validação, antes de armazenar o valor.
O padrão é: criar um atributo privado (com underline), e expor properties para get e set. Exemplo:
class Pessoa:
def __init__(self, nome):
self._nome = nome
@property
def nome(self):
return self._nome
@nome.setter
def nome(self, valor):
if not isinstance(valor, str):
raise TypeError("Nome deve ser uma string")
self._nome = valor
p = Pessoa("João")
p.nome = "Maria" # ok
print(p.nome) # Maria
# p.nome = 123 # TypeErrorTambém podemos definir um deleter com @nome.deleter, que é chamado ao usar del objeto.nome. É menos comum, mas útil para liberar recursos.
Validação
Properties são ideais para adicionar validação de dados. No setter, podemos verificar tipos, intervalos, formatos, etc., e levantar exceções apropriadas se os dados forem inválidos. Isso mantém a classe robusta e evita estados inconsistentes.
Exemplo: uma classe ContaBancaria com saldo que não pode ser negativo.
class ContaBancaria:
def __init__(self, saldo_inicial=0):
self._saldo = saldo_inicial
@property
def saldo(self):
return self._saldo
@saldo.setter
def saldo(self, valor):
if valor < 0:
raise ValueError("Saldo não pode ser negativo")
self._saldo = valor
def depositar(self, quantia):
self.saldo += quantia # usa o setter para validar
def sacar(self, quantia):
if quantia > self.saldo:
raise ValueError("Saldo insuficiente")
self.saldo -= quantia # usa o setter
c = ContaBancaria(100)
c.depositar(50)
print(c.saldo) # 150
c.sacar(200) # ValueError: Saldo insuficienteNote que usamos o setter internamente nos métodos, garantindo que a validação seja sempre aplicada.
Atributos computados
Properties também são usadas para criar atributos que são calculados dinamicamente a partir de outros dados. Eles não precisam de um atributo privado correspondente; o getter simplesmente computa e retorna o valor.
Exemplo: uma classe Retangulo com largura e altura, e uma property area que retorna o produto.
class Retangulo:
def __init__(self, largura, altura):
self._largura = largura
self._altura = altura
@property
def largura(self):
return self._largura
@largura.setter
def largura(self, valor):
if valor <= 0:
raise ValueError("Largura deve ser positiva")
self._largura = valor
@property
def altura(self):
return self._altura
@altura.setter
def altura(self, valor):
if valor <= 0:
raise ValueError("Altura deve ser positiva")
self._altura = valor
@property
def area(self):
return self._largura * self._altura
r = Retangulo(10, 5)
print(r.area) # 50
r.largura = 20
print(r.area) # 100Atributos computados são somente leitura (não têm setter) porque não faz sentido atribuir um valor que é derivado de outros. Eles são recalculados toda vez que são acessados, o que pode ser um problema de desempenho se o cálculo for caro; nesse caso, pode-se usar cache (por exemplo, armazenar o resultado e invalidar quando os dependentes mudarem).
Boas práticas e observações finais
- Use properties para expor atributos privados com controle, evitando getters e setters explícitos como em Java.
- Não abuse: se um atributo não precisa de validação ou lógica extra, use um atributo público simples. Properties devem ser adicionadas quando houver necessidade.
- Prefira
@propertya métodos comoget_nome()eset_nome()— é mais Pythonico. - Lembre-se de que o nome da property e do atributo privado podem ser diferentes, mas é comum usar o mesmo nome com underline para o privado.
- Para properties somente leitura, não defina setter; para leitura e escrita, defina ambos; para deleção, defina o deleter se necessário.
Referências
- Documentação oficial: property
- Python Descriptor HowTo
- Real Python: Python's property()
- GeeksforGeeks: @property decorator
- Programiz: Python @property
Exercícios
Crie uma classe
Termometroque armazena a temperatura em Celsius internamente, mas expõe uma propertyfahrenheitque retorna o valor convertido para Fahrenheit (F = C * 9/5 + 32). A property deve ser somente leitura.✓ Resposta:class Termometro: def __init__(self, celsius): self._celsius = celsius @property def fahrenheit(self): return self._celsius * 9/5 + 32Implemente uma classe
Produtocom atributos privados_nomee_preco. Crie properties com getters e setters, onde o setter de preço valida que o valor é um número positivo. O setter de nome deve garantir que o nome não seja vazio.✓ Resposta:class Produto: def __init__(self, nome, preco): self._nome = nome self._preco = preco @property def nome(self): return self._nome @nome.setter def nome(self, valor): if not valor.strip(): raise ValueError("Nome não pode ser vazio") self._nome = valor @property def preco(self): return self._preco @preco.setter def preco(self, valor): if valor <= 0: raise ValueError("Preço deve ser positivo") self._preco = valorNa classe
Retangulodo exemplo, adicione uma propertyperimetroque retorna o perímetro (2 * (largura + altura)).✓ Resposta:class Retangulo: def __init__(self, largura, altura): self._largura = largura self._altura = altura @property def largura(self): return self._largura @largura.setter def largura(self, valor): if valor <= 0: raise ValueError("Largura deve ser positiva") self._largura = valor @property def altura(self): return self._altura @altura.setter def altura(self, valor): if valor <= 0: raise ValueError("Altura deve ser positiva") self._altura = valor @property def area(self): return self._largura * self._altura @property def perimetro(self): return 2 * (self._largura + self._altura)Crie uma classe
Pessoacom um atributo privado_idade. A propertyidadedeve ter getter e setter, e o setter deve validar que a idade está entre 0 e 150. Além disso, adicione um deleter que define a idade como 0 (apenas para exemplo).✓ Resposta:class Pessoa: def __init__(self, idade): self._idade = idade @property def idade(self): return self._idade @idade.setter def idade(self, valor): if not (0 <= valor <= 150): raise ValueError("Idade deve estar entre 0 e 150") self._idade = valor @idade.deleter def idade(self): self._idade = 0Crie uma classe
Livrocom atributos privados_titulo,_autore_ano. Crie uma propertydescricaoque retorna uma string formatada: "'Titulo' por Autor (Ano)". A property deve ser somente leitura.✓ Resposta:class Livro: def __init__(self, titulo, autor, ano): self._titulo = titulo self._autor = autor self._ano = ano @property def descricao(self): return f"'{self._titulo}' por {self._autor} ({self._ano})"