Propriedades permitem controlar o acesso a atributos de classe.
Elas transformam métodos em atributos virtuais com lógica personalizada.
Primeiramente, @property cria um getter para o atributo.
Por exemplo, @property seguido de def nome(self).
Além disso, @nome.setter define um setter para validação.
O decorador @nome.deleter controla a deleção do atributo.
A voz passiva é usada aqui: “os valores são validados antes de serem armazenados”.
Quando utilizar propriedades? Em atributos que precisam de validação.
Também para criar atributos calculados ou somente leitura.
Propriedades mantêm a sintaxe simples de atributos públicos.
Porém, elas adicionam lógica de negócio por trás dos panos.
Vamos explorar cada tipo com exemplos práticos.
Três subtítulos guiarão você pelas propriedades em Python.
Ao final, você projetará classes mais seguras e expressivas.
Getter: lendo valores com @property
O decorador @property transforma um método em getter.
Você acessa o valor como um atributo normal, sem parênteses.
Quando usar getter? Para atributos calculados dinamicamente.
Também para expor dados internos de forma controlada.
A voz passiva é aplicada: “o valor é calculado a cada acesso”.
Exemplo de getter:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
class Circulo: """Círculo com propriedade calculada.""" def __init__(self, raio): self._raio = raio # Atributo "protegido" @property def raio(self): """Getter para o raio.""" print("Acessando o raio") return self._raio @property def area(self): """Propriedade calculada (somente leitura).""" print("Calculando área...") return 3.14159 * self._raio ** 2 @property def circunferencia(self): """Outra propriedade calculada.""" return 2 * 3.14159 * self._raio # Demonstração print("=== Getter com @property ===\n") c = Circulo(5) # Acesso como atributo (sem parênteses) print(f"Raio: {c.raio}") print(f"Área: {c.area:.2f}") print(f"Circunferência: {c.circunferencia:.2f}") # Tentativa de atribuição direta (falha pois não há setter) try: c.area = 100 except AttributeError as e: print(f"Erro: {e}") class Retangulo: """Retângulo com getter formatado.""" def __init__(self, largura, altura): self._largura = largura self._altura = altura @property def largura(self): return self._largura @property def altura(self): return self._altura @property def area(self): return self._largura * self._altura @property def perimetro(self): return 2 * (self._largura + self._altura) @property def dimensoes(self): """Retorna tupla com dimensões.""" return (self._largura, self._altura) print("\n=== Retângulo com Getters ===") r = Retangulo(10, 5) print(f"Largura: {r.largura}") print(f"Altura: {r.altura}") print(f"Área: {r.area}") print(f"Perímetro: {r.perimetro}") print(f"Dimensões: {r.dimensoes}") |
Getters são ideais para atributos derivados ou somente leitura. Eles mantêm a interface limpa e consistente.
Setter: validando valores com @nome.setter
O setter permite validar dados antes de armazená-los.
Use @nome.setter no método que recebe o novo valor.
Quando usar setter? Para atributos que exigem validação.
Por exemplo, idade negativa ou saldo insuficiente.
A voz passiva é aplicada: “a validação é aplicada automaticamente”.
Exemplo de setter:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
class Pessoa: """Pessoa com validação de idade e nome.""" def __init__(self, nome, idade): self._nome = None self._idade = None self.nome = nome # Usa o setter self.idade = idade # Usa o setter @property def nome(self): return self._nome @nome.setter def nome(self, valor): """Valida o nome (não pode ser vazio).""" if not valor or not valor.strip(): raise ValueError("Nome não pode ser vazio") self._nome = valor.strip().title() @property def idade(self): return self._idade @idade.setter def idade(self, valor): """Valida idade (entre 0 e 150).""" if not isinstance(valor, int): raise TypeError("Idade deve ser um número inteiro") if valor < 0 or valor > 150: raise ValueError("Idade deve estar entre 0 e 150") self._idade = valor @property def pode_votar(self): """Propriedade calculada (somente leitura).""" return self._idade >= 16 # Demonstração print("=== Setter com Validação ===\n") p = Pessoa("ana maria", 25) print(f"Nome: {p.nome}") print(f"Idade: {p.idade}") print(f"Pode votar: {p.pode_votar}") # Tentativas com valores inválidos try: p.nome = "" except ValueError as e: print(f"Erro nome vazio: {e}") try: p.idade = -5 except ValueError as e: print(f"Erro idade negativa: {e}") try: p.idade = 200 except ValueError as e: print(f"Erro idade excessiva: {e}") try: p.idade = "trinta" except TypeError as e: print(f"Erro tipo inválido: {e}") # Valores válidos p.nome = " joão silva " p.idade = 30 print(f"\nApós atualizações válidas:") print(f"Nome: {p.nome}") print(f"Idade: {p.idade}") class ContaBancaria: """Conta com validação de saldo.""" def __init__(self, titular, saldo_inicial=0): self.titular = titular self._saldo = 0 self.saldo = saldo_inicial # Usa setter @property def saldo(self): return self._saldo @saldo.setter def saldo(self, valor): """Valida saldo (não pode ser negativo).""" if valor < 0: raise ValueError("Saldo não pode ser negativo") self._saldo = valor def depositar(self, valor): if valor <= 0: raise ValueError("Valor do depósito deve ser positivo") self._saldo += valor def sacar(self, valor): if valor <= 0: raise ValueError("Valor do saque deve ser positivo") if valor > self._saldo: raise ValueError("Saldo insuficiente") self._saldo -= valor print("\n=== Conta com Setter ===") conta = ContaBancaria("Carlos", 1000) print(f"Saldo inicial: R${conta.saldo:.2f}") conta.depositar(500) print(f"Após depósito: R${conta.saldo:.2f}") conta.sacar(200) print(f"Após saque: R${conta.saldo:.2f}") try: conta.saldo = -50 except ValueError as e: print(f"Erro: {e}") |
Setters protegem a integridade dos seus dados. Eles centralizam a lógica de validação em um único lugar.
Deleter: controlando remoção de atributos
O deleter define o comportamento de del objeto.atributo.
Use @nome.deleter para impedir ou logar deleções.
Quando usar deleter? Em atributos que não devem ser removidos.
Também para realizar limpeza antes da remoção.
A voz passiva é aplicada: “a deleção é interceptada pelo deleter”.
Exemplo de deleter:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
class Configuracao: """Configuração que impede deleção de atributos críticos.""" def __init__(self): self._db_url = "postgresql://localhost:5432" self._api_key = "segredo-123" self._debug = True @property def db_url(self): return self._db_url @db_url.deleter def db_url(self): raise AttributeError("Não é possível deletar a URL do banco") @property def api_key(self): return "********" # Esconde o valor real @api_key.deleter def api_key(self): print("Log: Tentativa de deletar api_key (negado)") raise AttributeError("Não é possível deletar a chave da API") @property def debug(self): return self._debug @debug.setter def debug(self, valor): if not isinstance(valor, bool): raise TypeError("Debug deve ser booleano") self._debug = valor @debug.deleter def debug(self): print("Resetando debug para False em vez de deletar") self._debug = False # Demonstração print("=== Deleter em Ação ===\n") config = Configuracao() print(f"DB URL: {config.db_url}") print(f"API Key: {config.api_key}") print(f"Debug: {config.debug}") # Tentativas de deleção try: del config.db_url except AttributeError as e: print(f"Erro ao deletar db_url: {e}") try: del config.api_key except AttributeError as e: print(f"Erro ao deletar api_key: {e}") # Deleter com ação alternativa (não levanta erro) del config.debug print(f"Debug após deleção (resetado): {config.debug}") # Exemplo prático: Cache com deleção controlada class Cache: """Cache que registra quando itens são deletados.""" def __init__(self): self._dados = {} self._acessos = {} @property def dados(self): """Retorna cópia dos dados (protege o original).""" return self._dados.copy() @dados.deleter def dados(self): """Limpa o cache e registra.""" print(f"LIMPEZA: Removendo {len(self._dados)} itens do cache") self._dados.clear() self._acessos.clear() def adicionar(self, chave, valor): self._dados[chave] = valor self._acessos[chave] = 0 def obter(self, chave): if chave in self._dados: self._acessos[chave] += 1 return self._dados[chave] return None @property def estatisticas(self): return { "total_itens": len(self._dados), "acessos": self._acessos.copy() } print("\n=== Cache com Deleter ===") cache = Cache() cache.adicionar("usuario_1", "Ana") cache.adicionar("usuario_2", "Carlos") cache.adicionar("usuario_3", "Maria") print(f"Dados: {cache.dados}") print(f"Estatísticas: {cache.estatisticas}") # Deleta todo o cache via property del cache.dados print(f"Após deleção: {cache.dados}") # Exemplo completo: Temperatura com conversão automática class Temperatura: """Temperatura com propriedade em Celsius e Fahrenheit.""" def __init__(self, celsius=0): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, valor): if valor < -273.15: raise ValueError("Temperatura abaixo do zero absoluto") self._celsius = valor @celsius.deleter def celsius(self): print("Resetando temperatura para 0°C") self._celsius = 0 @property def fahrenheit(self): """Propriedade calculada (conversão).""" return (self._celsius * 9/5) + 32 @fahrenheit.setter def fahrenheit(self, valor): """Permite definir temperatura em Fahrenheit.""" self.celsius = (valor - 32) * 5/9 def __str__(self): return f"{self.celsius}°C = {self.fahrenheit:.1f}°F" print("\n=== Temperatura com Propriedades ===") temp = Temperatura(25) print(temp) temp.fahrenheit = 100 print(f"Após definir 100°F: {temp}") del temp.celsius # Reseta para 0 print(f"Após deleção: {temp}") |
Deleters oferecem controle fino sobre a remoção de atributos. A fórmula de encapsulamento com propriedades: \(E = \text{getter} + \text{setter} + \text{deleter}\) Use getter para acesso controlado ou valores calculados. Use setter para validação e transformação de dados. Use deleter para limpeza ou prevenção de deleções. Propriedades mantêm sua API limpa e seu código seguro.