✅ Artigo atualizado em Setembro 2026 Novas seções sobre setter sem decorador (função
property()), propriedades somente leitura,cached_propertye@propertyvs dataclass, tabela de decisão, erros comuns com traceback real e 8 exercícios resolvidos.
Resposta rápida
@property é um decorador que transforma um método em atributo de leitura: você acessa obj.diametro sem parênteses e o método roda por trás. Para permitir escrita e remoção com validação, defina @diametro.setter e @diametro.deleter em métodos com o mesmo nome.
1
2
3
4
5
6
class Circulo:
raio = 5
@property
def diametro(self):
return self.raio * 2
print(Circulo().diametro) # 10 (sem parênteses)
Resumo em 30 segundos:
- Getter = método com
@property; setter =@nome.setter; deleter =@nome.deleter. Os três métodos têm o mesmo nome. - O valor real fica em um atributo interno (
self._nome). Usarself.nomedentro do setter causaRecursionError. - Sem setter, a propriedade é somente leitura: atribuir um valor gera
AttributeError. - Propriedade se acessa sem parênteses:
obj.area()quebra comTypeError: 'int' object is not callable. -
@propertyé atalho para a funçãoproperty(fget, fset, fdel, doc); para calcular uma vez só e guardar, usefunctools.cached_property.
Salve salve Pythonista!
@property transforma métodos em atributos, permitindo validação, cálculos e encapsulamento sem mudar a interface pública da classe!
Neste guia, você vai aprender:
- ✅ Getters/Setters Pythônicos - Sem
.get_x()/.set_x() - ✅ Validação automática - Dados sempre válidos
- ✅ Atributos computados - Cálculos sob demanda
- ✅ Somente leitura e cache -
AttributeErrorao atribuir ecached_property - ✅ Casos práticos - Temperatura, idade, preços
- ✅ Erros comuns e exercícios resolvidos - Com a saída real de cada código
O @property é um decorador que permite transformar métodos de uma classe em propriedades, tornando o código mais elegante e fácil de entender.
Sem mais delongas, vamos nessa!
Vá Direto ao Assunto…
- Propriedades em Python
- O decorador
@property - Getters e Setters
- Deletar propriedades
- Como definir um setter sem @property: a função property()
- Propriedade somente leitura e cached_property
- Casos de uso do
@property - Casos Práticos Adicionais
- @property vs Atributos Públicos
- @property vs dataclass: qual usar?
- Erros comuns
- Exercícios resolvidos
Leia também
Ao criar classes em Python, é comum definir métodos que retornam ou modificam valores de atributos.
Se ainda não sabe sobre Classes em Python, leia nosso outro artigo sobre Classes no Python
No entanto, esses métodos muitas vezes têm nomes diferentes dos atributos e são acessados como funções.
Para melhorar a legibilidade do código e fornecer uma sintaxe mais intuitiva, podemos usar o decorador @property.
Propriedades em Python
Antes de mergulharmos no @property, é importante entender o conceito de propriedades em Python.
Uma propriedade é um atributo gerenciado: em vez de ler ou gravar um valor guardado diretamente no objeto, o Python chama um método seu. O valor pode ser calculado na hora (como uma área) ou guardado em um atributo interno depois de passar por uma validação.
Quando um atributo é uma propriedade, ele é acessado e modificado como qualquer outro atributo, mas na verdade, por trás dos panos, um método customizado é chamado.
Ao utilizar propriedades, podemos ter controle sobre o acesso e modificação de atributos de uma classe, permitindo realizar validações, conversões de dados ou cálculos adicionais antes de retornar ou definir o valor. É a forma idiomática de aplicar o encapsulamento que você viu na introdução à Programação Orientada a Objetos no Python.
A referência oficial está na documentação da função embutida property().
O decorador @property
Em Python, o decorador @property é usado para transformar um método em uma propriedade de uma classe.
Ele permite que um método seja acessado como atributo, sem a necessidade de chamá-lo como uma função.
Vamos começar com um exemplo simples para ilustrar como o @property funciona.
Suponha que temos a classe Retangulo que representa um retângulo e possui os atributos largura e altura.
Para calcular a área do retângulo, poderíamos ter um método chamado calcular_area, como mostrado abaixo:
1
2
3
4
5
6
7
class Retangulo:
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
def calcular_area(self):
return self.largura * self.altura
Neste caso, para calcular a área do retângulo, precisamos chamar o método calcular_area() explicitamente:
1
2
3
retangulo = Retangulo(5, 3)
area = retangulo.calcular_area()
print(area)
E a saída seria:
1
15
Agora, vamos utilizar o @property para transformar o método calcular_area() em uma propriedade da classe Retangulo:
1
2
3
4
5
6
7
8
class Retangulo:
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
@property
def area(self):
return self.largura * self.altura
Note o uso do decorador @property antes do método area(). Agora, podemos acessar a área do retângulo como se fosse um atributo:
1
2
retangulo = Retangulo(5, 3)
print(retangulo.area) # Saída: 15
Agora, a chamada retangulo.area retorna o valor da área sem a necessidade de chamarmos explicitamente o método.
Repare que area é um atributo calculado: ele não fica guardado em lugar nenhum. Se a largura mudar, a próxima leitura de retangulo.area já devolve o valor novo.
Estou construindo o Ebookr.ai, uma plataforma onde você cria ebooks profissionais com IA sobre qualquer assunto - do zero ao PDF pronto, com capas e infográficos gerados automaticamente. Dá uma olhada!
Getters e Setters
Em muitos casos, queremos não apenas obter o valor calculado de uma propriedade, mas também definir seu valor.
Para isso, utilizamos os métodos getter e setter.
O método getter é responsável por retornar o valor da propriedade quando ela é acessada.
Utilizamos o decorador @property para definir o getter, como já vimos anteriormente.
O método setter, por sua vez, é usado para definir o valor da propriedade quando ela é modificada.
Para definir o setter, utilizamos o mesmo nome da propriedade seguido pelo decorador @nomedapropriedade.setter.
Vamos expandir nosso exemplo anterior para incluir um setter para modificar a largura do retângulo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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, nova_largura):
if nova_largura > 0:
self._largura = nova_largura
else:
raise ValueError("A largura deve ser maior que 0.")
@property
def area(self):
return self.largura * self.altura
Neste exemplo, criamos um método largura para funcionar como o getter da propriedade largura e um método largura.setter para funcionar como o setter.
O método setter verifica se o novo valor da largura é maior que zero e, em caso positivo, atualiza o atributo _largura.
Caso contrário, lança uma exceção ValueError informando que a largura deve ser maior que zero.
Agora, podemos usar a propriedade largura para obter e modificar o valor da largura do retângulo:
1
2
3
4
5
6
7
retangulo = Retangulo(5, 3)
print(retangulo.largura) # Saída: 5
retangulo.largura = 7
print(retangulo.largura) # Saída: 7
retangulo.largura = -1 # Lança uma exceção ValueError
A última linha interrompe o programa com:
1
ValueError: A largura deve ser maior que 0.
Podemos ver que, ao acessar a propriedade largura, o método largura() é chamado e retorna o valor atual da largura.
Da mesma forma, ao atribuir um novo valor à propriedade largura, o método largura.setter é executado e valida o novo valor antes de atualizar _largura.
Para tratar esse erro sem derrubar o programa, use try/except, como mostramos no artigo sobre tratamento de erros e exceções no Python.
Getter e setter pythônicos vs métodos get_ e set_
Quem vem de Java ou C# costuma escrever um método get_ e um set_ para cada atributo. Funciona, mas o código de quem usa a classe fica mais verboso:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ContaJava:
def __init__(self, saldo):
self._saldo = saldo
def get_saldo(self):
return self._saldo
def set_saldo(self, valor):
if valor < 0:
raise ValueError("Saldo não pode ser negativo")
self._saldo = valor
conta = ContaJava(100)
conta.set_saldo(conta.get_saldo() + 50)
print(conta.get_saldo()) # 150
Com @property, a mesma regra vira um atributo comum para quem usa a classe, e até operadores como += passam pela validação:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Conta:
def __init__(self, saldo):
self.saldo = saldo # passa pelo setter e já valida
@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
conta = Conta(100)
conta.saldo += 50
print(conta.saldo) # 150
Repare na linha 3: o __init__ usa self.saldo = saldo (sem underline), então até a criação do objeto passa pelo setter e Conta(-10) já levanta ValueError: Saldo não pode ser negativo. No Retangulo acima, o __init__ grava direto em self._largura e, por isso, Retangulo(-5, 3) é aceito sem reclamar.
A grande vantagem do jeito pythônico: você pode começar com um atributo público simples e, só quando precisar de validação, trocá-lo por uma property sem quebrar nenhum código que já usa conta.saldo.
Está curtindo esse conteúdo? ![]()
Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?
Deletar propriedades
Além de obter e definir o valor de uma propriedade, também podemos excluí-la utilizando o método deleter.
Para definir o deleter de uma propriedade, utilizamos o decorador @nomedapropriedade.deleter.
Vamos adicionar um método deleter à classe Retangulo para excluir a propriedade largura:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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, nova_largura):
if nova_largura > 0:
self._largura = nova_largura
else:
raise ValueError("A largura deve ser maior que 0.")
@largura.deleter
def largura(self):
del self._largura
@property
def area(self):
return self.largura * self.altura
Agora podemos excluir a propriedade largura utilizando o comando del:
1
2
3
4
5
6
retangulo = Retangulo(5, 3)
print(retangulo.largura) # Saída: 5
del retangulo.largura
print(retangulo.largura)
Ao tentar acessar a propriedade na linha print(retangulo.largura) o seguinte erro será lançado:
1
AttributeError: 'Retangulo' object has no attribute '_largura'
Como definir um setter sem @property: a função property()
O @property é só um atalho. Por baixo, ele chama a função embutida property(fget, fset, fdel, doc), que recebe o getter, o setter, o deleter e um texto de documentação. Então dá para criar exatamente a mesma propriedade sem nenhum decorador, o que responde a uma pergunta comum em listas de exercícios: “como definir um setter para o atributo nome sem @property?”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Pessoa:
def __init__(self, nome):
self.nome = nome
def get_nome(self):
return self._nome
def set_nome(self, valor):
if not valor.strip():
raise ValueError("O nome não pode ser vazio")
self._nome = valor.strip().title()
def del_nome(self):
del self._nome
nome = property(get_nome, set_nome, del_nome, "Nome da pessoa")
p = Pessoa(" ana souza ")
print(p.nome) # Ana Souza
p.nome = "bia lima"
print(p.nome) # Bia Lima
print(Pessoa.nome.__doc__) # Nome da pessoa
A linha 16 é a que faz a mágica: a partir dela, ler p.nome chama get_nome, atribuir p.nome = ... chama set_nome e del p.nome chama del_nome. Os métodos get_nome e set_nome continuam existindo e também podem ser chamados diretamente, se você preferir.
Essa equivalência explica por que todos os métodos precisam ter o mesmo nome na versão com decorador. O código
1
2
3
4
5
@property
def nome(self): ...
@nome.setter
def nome(self, valor): ...
é o mesmo que escrever nome = property(nome) e depois nome = nome.setter(nome_novo). O .setter() devolve uma cópia da property com o setter acrescentado, e essa cópia é guardada de novo no nome nome. Se o segundo método tiver outro nome, a cópia com setter vai parar em outra variável e a nome original continua sem setter. Para entender a sintaxe @ em detalhes, veja nosso guia para dominar decorators em Python.
Propriedade somente leitura e cached_property
Uma property com getter e sem setter é somente leitura. É o jeito mais simples de expor um valor que o código de fora pode consultar, mas não pode alterar:
1
2
3
4
5
6
7
8
9
10
11
class Pedido:
def __init__(self, itens):
self._itens = list(itens)
@property
def total(self):
return sum(preco for _, preco in self._itens)
pedido = Pedido([("Café", 8.5), ("Pão", 1.5)])
print(pedido.total) # 10.0
pedido.total = 99
A última linha falha. A mensagem depende da versão do Python. No Python 3.10:
1
AttributeError: can't set attribute 'total'
Do Python 3.11 em diante, a mensagem ficou mais explícita:
1
AttributeError: property 'total' of 'Pedido' object has no setter
“Somente leitura” aqui é uma proteção contra atribuição acidental, não segurança: o atributo interno _itens continua acessível. O underline é uma convenção que diz “não mexa nisso de fora da classe”.
cached_property: calcular uma vez e guardar
Uma property comum roda o método a cada leitura. Se o cálculo for caro (ler um arquivo, consultar uma API, processar uma lista enorme) e os dados não mudam, use functools.cached_property, disponível desde o Python 3.8:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from functools import cached_property
class Relatorio:
def __init__(self, numeros):
self.numeros = numeros
@cached_property
def media(self):
print("Calculando a média...")
return sum(self.numeros) / len(self.numeros)
r = Relatorio([10, 20, 30])
print(r.media)
print(r.media) # usa o cache, não imprime "Calculando..."
r.numeros.append(40)
print(r.media) # ainda 20.0: o cache não sabe que a lista mudou
del r.media # invalida o cache
print(r.media)
Saída:
1
2
3
4
5
6
Calculando a média...
20.0
20.0
20.0
Calculando a média...
25.0
O que muda em relação ao @property:
- O resultado é guardado no
__dict__da instância na primeira leitura; as próximas nem chamam o método. - O valor não se atualiza sozinho. Se os dados de origem mudarem, apague o cache com
del obj.atributo. - Não é somente leitura:
r.media = 0sobrescreve o valor guardado sem erro. - Não funciona em classes com
__slots__(sem__dict__).
Regra prática: use @property quando o valor depende de dados que mudam e cached_property quando o cálculo é caro e o resultado vale para toda a vida do objeto.
Casos de uso do @property
O decorador @property é muito útil em situações em que precisamos controlar o acesso aos atributos de uma classe.
Aqui estão alguns exemplos de casos de uso comuns:
- Conversão de tipos: podemos usar
@propertypara converter automaticamente tipos de dados. Por exemplo, podemos ter um atributodataque é armazenado como uma string e uma propriedadedataque devolve o valor convertido em um objetodatetime. - Verificação de validade: podemos adicionar validações em um
setterpara garantir que os atributos estão dentro dos limites aceitáveis. Por exemplo, podemos ter um atributoidadeque precisa ser um número positivo e, caso contrário, levanta um erro. - Uso de cache: podemos utilizar uma propriedade para fazer cache de um valor calculado, evitando recalcular a cada vez que a propriedade é acessada. Para isso, a biblioteca padrão já oferece o
functools.cached_property(veja a seção anterior). - Acesso a dados externos: podemos usar propriedades para acessar e atualizar dados em bancos de dados externos ou sistemas remotos. Dessa forma, podemos manter a interface do objeto consistente, independentemente de onde os dados são armazenados.
Casos Práticos Adicionais
1. Conversor de Temperatura
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
class Temperatura:
def __init__(self, celsius):
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
@property
def fahrenheit(self): # Somente leitura
return self._celsius * 9/5 + 32
@property
def kelvin(self): # Somente leitura
return self._celsius + 273.15
temp = Temperatura(25)
print(f"{temp.celsius}°C = {temp.fahrenheit}°F = {temp.kelvin}K")
# 25°C = 77.0°F = 298.15K
2. Produto com Preço Calculado
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Produto:
def __init__(self, nome, preco_base, imposto=0.1):
self.nome = nome
self._preco_base = preco_base
self.imposto = imposto
@property
def preco_base(self):
return self._preco_base
@preco_base.setter
def preco_base(self, valor):
if valor <= 0:
raise ValueError("Preço deve ser positivo")
self._preco_base = valor
@property
def preco_final(self): # Calculado automaticamente
return round(self._preco_base * (1 + self.imposto), 2)
produto = Produto("Mouse", 100)
print(produto.preco_final) # 110.0
produto.preco_base = 150
print(produto.preco_final) # 165.0
O round(..., 2) na linha 19 não é enfeite: sem ele, 100 * 1.1 devolve 110.00000000000001, por causa da forma como números de ponto flutuante são representados. Para valores monetários em sistemas reais, prefira o módulo decimal.
3. Pessoa com Idade Calculada
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
from datetime import date
class Pessoa:
def __init__(self, nome, data_nascimento):
self.nome = nome
self._data_nascimento = data_nascimento
@property
def data_nascimento(self):
return self._data_nascimento
@data_nascimento.setter
def data_nascimento(self, valor):
if valor > date.today():
raise ValueError("Data de nascimento no futuro!")
self._data_nascimento = valor
@property
def idade(self): # Sempre atualizado!
hoje = date.today()
return hoje.year - self._data_nascimento.year - (
(hoje.month, hoje.day) < (self._data_nascimento.month, self._data_nascimento.day)
)
pessoa = Pessoa("Alice", date(1990, 5, 15))
print(pessoa.idade) # 36 (rodando em setembro de 2026); muda com a data atual
@property vs Atributos Públicos
Quando Usar @property?
✅ Use @property quando:
- Precisa validar valores
- Atributo é calculado (não armazenado)
- Precisa controlar acesso (read-only)
- Pode precisar mudar lógica no futuro
- Lazy loading (calcular só quando necessário)
Quando NÃO usar @property?
❌ Use atributo simples quando:
- Atributo é apenas armazenamento (sem lógica)
- Performance crítica (property tem overhead mínimo)
- Não precisa validação
- Classe é simples (dataclass)
1
2
3
4
5
6
7
8
9
10
11
12
13
# ❌ Property desnecessária
class Usuario:
def __init__(self, nome):
self._nome = nome
@property
def nome(self):
return self._nome # Só retorna, sem lógica
# ✅ Atributo público direto
class Usuario:
def __init__(self, nome):
self.nome = nome # Mais simples!
Escolher entre @property e dataclass é decisão de design, e design de código bom se aprende praticando POO de verdade. Essa é a espinha dorsal da Jornada Python, do básico ao avançado:
@property vs dataclass: qual usar?
Muita gente que pesquisa @property também esbarra em @dataclass, porque os dois aparecem em cima de classes. Eles resolvem problemas diferentes:
-
@dataclassage na classe inteira: gera__init__,__repr__e__eq__a partir das anotações de tipo, eliminando código repetitivo. -
@propertyage em um atributo: roda um método a cada leitura (e, com setter, a cada escrita).
E eles combinam bem. Uma dataclass pode validar os dados na criação com __post_init__ e expor campos calculados com @property:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from dataclasses import dataclass
@dataclass
class Produto:
nome: str
preco: float
quantidade: int = 1
def __post_init__(self):
if self.preco <= 0:
raise ValueError("Preço deve ser positivo")
@property
def total(self):
return self.preco * self.quantidade
p = Produto("Caneta", 2.5, 4)
print(p) # Produto(nome='Caneta', preco=2.5, quantidade=4)
print(p.total) # 10.0
p.preco = -1 # passa sem erro: __post_init__ só roda na criação
print(p.preco) # -1
Repare na linha 20: o __post_init__ só valida na criação. Se você precisa validar a cada atribuição, a ferramenta certa é a property com setter. E se quiser um objeto que não muda nunca, use @dataclass(frozen=True): qualquer atribuição depois da criação gera dataclasses.FrozenInstanceError: cannot assign to field 'preco'.
Tabela de decisão: quando usar cada um
| Situação | Use | Por quê |
|---|---|---|
| Atributo só guarda um valor, sem regra | Atributo público simples | Menos código; dá para virar property depois sem quebrar nada |
| Valor derivado de outros atributos (área, total, idade) |
@property sem setter |
Recalcula a cada leitura e é somente leitura |
| Validar ou converter a cada atribuição |
@property + @x.setter
|
O setter roda em toda atribuição, inclusive no __init__
|
| Cálculo caro sobre dados que não mudam | functools.cached_property |
Calcula na primeira leitura e guarda o resultado |
| Classe que é basicamente um registro de dados | @dataclass |
Gera __init__, __repr__ e __eq__ automaticamente |
| Validar só na criação do objeto |
@dataclass + __post_init__
|
Simples, mas não protege atribuições posteriores |
| Objeto que nunca muda depois de criado | @dataclass(frozen=True) |
Qualquer atribuição gera FrozenInstanceError
|
| Ação com parâmetros extras ou efeito colateral (salvar, enviar) | Método comum, chamado com ()
|
Property deve parecer um atributo: leitura barata e sem surpresas |
Erros comuns
Estes são os erros que mais aparecem ao usar @property, com a mensagem real que o Python mostra.
RecursionError: usar self.nome dentro do próprio setter
1
2
3
4
5
6
7
8
9
10
11
12
13
class Pessoa:
def __init__(self, nome):
self.nome = nome
@property
def nome(self):
return self.nome
@nome.setter
def nome(self, valor):
self.nome = valor
p = Pessoa("Ana")
1
2
3
self.nome = valor
[Previous line repeated 994 more times]
RecursionError: maximum recursion depth exceeded
Dentro do setter, self.nome = valor chama o próprio setter de novo, infinitamente. O getter com return self.nome tem o mesmo problema. Correção: guarde o valor em um atributo com outro nome, por convenção self._nome, tanto no getter (return self._nome) quanto no setter (self._nome = valor).
AttributeError: atribuir a uma property sem setter
1
2
3
4
5
6
7
8
9
10
11
class Retangulo:
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
@property
def area(self):
return self.largura * self.altura
r = Retangulo(5, 3)
r.area = 20
No Python 3.10 a última linha do traceback é:
1
AttributeError: can't set attribute 'area'
No Python 3.11 ou mais recente, a mesma situação mostra AttributeError: property 'area' of 'Retangulo' object has no setter. Se area deve mesmo ser somente leitura, o erro é o comportamento esperado: altere largura ou altura. Se a atribuição precisa funcionar, crie um método area decorado com @area.setter.
Esquecer o @x.setter ou dar outro nome ao método do setter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Pessoa:
def __init__(self, nome):
self._nome = nome
@property
def nome(self):
return self._nome
@nome.setter
def set_nome(self, valor): # nome diferente da property!
self._nome = valor.strip().title()
p = Pessoa("Ana")
p.nome = " bia "
1
AttributeError: can't set attribute 'nome'
(No Python 3.11+: AttributeError: property 'nome' of 'Pessoa' object has no setter.) O @nome.setter criou uma property nova chamada set_nome; a nome continua sem setter. Se você esquecer o decorador @nome.setter e só escrever outro def nome(self, valor), é pior: o segundo def substitui a property por um método comum, e print(p.nome) passa a mostrar algo como <bound method Pessoa.nome of <__main__.Pessoa object at 0x...>> em vez do nome. Correção: o método do setter precisa ter exatamente o mesmo nome da property e estar decorado com @nome.setter.
TypeError: 'int' object is not callable: chamar a property como método
1
2
3
4
5
6
7
8
9
10
11
class Retangulo:
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
@property
def area(self):
return self.largura * self.altura
r = Retangulo(5, 3)
print(r.area())
1
TypeError: 'int' object is not callable
r.area já executa o método e devolve 15; os parênteses tentam “chamar” o número 15. O tipo na mensagem muda conforme o retorno ('float' object is not callable, 'str' object is not callable…). Correção: acesse sem parênteses, print(r.area).
Validação ignorada no __init__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Retangulo:
def __init__(self, largura, altura):
self._largura = largura # grava direto, sem passar pelo setter
self.altura = altura
@property
def largura(self):
return self._largura
@largura.setter
def largura(self, valor):
if valor <= 0:
raise ValueError("A largura deve ser maior que 0.")
self._largura = valor
r = Retangulo(-5, 3)
print(r.largura) # -5
Não aparece erro nenhum, e esse é o problema: o objeto nasce inválido. Correção: no __init__, escreva self.largura = largura (sem underline) para que a criação também passe pelo setter. Aí Retangulo(-5, 3) levanta ValueError: A largura deve ser maior que 0.
Exercícios resolvidos
Tente resolver cada exercício antes de abrir a solução. Todas as saídas foram conferidas rodando o código no Python 3.10.
Exercício 1. Crie a classe Quadrado, que recebe lado no construtor e tem as propriedades somente leitura area e perimetro. Mostre os dois valores para um quadrado de lado 5.
Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Quadrado:
def __init__(self, lado):
self.lado = lado
@property
def area(self):
return self.lado ** 2
@property
def perimetro(self):
return self.lado * 4
q = Quadrado(5)
print(q.area, q.perimetro)
Saída: 25 20
As duas propriedades são calculadas a partir de lado a cada leitura, então se você fizer q.lado = 6 elas já refletem o novo valor.
Exercício 2. Crie a classe Pessoa com uma propriedade nome cujo setter remove espaços das pontas e deixa cada palavra com inicial maiúscula. O construtor também deve passar pelo setter. Teste com " joão souza ".
Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Pessoa:
def __init__(self, nome):
self.nome = nome
@property
def nome(self):
return self._nome
@nome.setter
def nome(self, valor):
self._nome = valor.strip().title()
p = Pessoa(" joão souza ")
print(p.nome)
Saída: João Souza
Como o __init__ usa self.nome = nome, o valor passa pelo setter já na criação. O valor tratado fica guardado em self._nome.
Exercício 3. Crie a classe Aluno com uma propriedade nota que só aceita valores de 0 a 10; fora disso, levante ValueError. Tente atribuir 11, capture o erro e mostre a mensagem e a nota que ficou.
Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Aluno:
def __init__(self, nome, nota):
self.nome = nome
self.nota = nota
@property
def nota(self):
return self._nota
@nota.setter
def nota(self, valor):
if not 0 <= valor <= 10:
raise ValueError("A nota deve estar entre 0 e 10")
self._nota = valor
aluno = Aluno("Ana", 8)
try:
aluno.nota = 11
except ValueError as erro:
print(erro)
print(aluno.nota)
Saída:
1
2
A nota deve estar entre 0 e 10
8
O setter levanta a exceção antes de gravar, então a nota anterior (8) é preservada.
Exercício 4 (estilo prova). Considere o código:
1
2
3
4
5
6
7
8
9
10
class Carrinho:
def __init__(self):
self.itens = [30, 20]
@property
def total(self):
return sum(self.itens)
c = Carrinho()
c.total = 50
O que acontece ao executar a última linha?
a) total passa a valer 50
b) O Python levanta AttributeError
c) O Python levanta ValueError
d) A atribuição é ignorada silenciosamente e total continua 50
Ver solução
Resposta: b.
Saída (Python 3.10): AttributeError: can't set attribute 'total'
total tem apenas getter, então é somente leitura. No Python 3.11+ a mensagem é AttributeError: property 'total' of 'Carrinho' object has no setter. A alternativa d é uma pegadinha: o total é 50 por coincidência (30 + 20), mas a atribuição não é ignorada, ela gera erro.
Exercício 5 (estilo prova). Sobre o decorador @property em Python, assinale a alternativa correta:
a) Um método decorado com @property deve ser chamado com parênteses, como obj.area()
b) @property permite acessar um método como atributo, e um setter pode ser definido com @nome.setter em um método de mesmo nome
c) @property só funciona em classes decoradas com @dataclass
d) @property torna o atributo _nome privado, impedindo qualquer acesso a ele de fora da classe
Ver solução
Resposta: b.
1
2
3
4
5
6
7
8
9
10
class Conta:
def __init__(self, saldo):
self._saldo = saldo
@property
def saldo(self):
return self._saldo
c = Conta(100)
print(c._saldo)
Saída: 100
A alternativa a está errada porque a property é acessada sem parênteses (com eles, dá TypeError). A c está errada porque @property funciona em qualquer classe. A d está errada, como mostra o código: _saldo continua acessível; o underline é só uma convenção.
Exercício 6. Crie a classe Temperatura que guarda apenas celsius e tem uma propriedade fahrenheit com getter e setter. Ao atribuir um valor em Fahrenheit, o setter deve converter e atualizar celsius. Atribua 212 °F e mostre as duas escalas.
Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Temperatura:
def __init__(self, celsius=0):
self.celsius = celsius
@property
def fahrenheit(self):
return self.celsius * 9 / 5 + 32
@fahrenheit.setter
def fahrenheit(self, valor):
self.celsius = (valor - 32) * 5 / 9
t = Temperatura()
t.fahrenheit = 212
print(t.celsius, t.fahrenheit)
Saída: 100.0 212.0
Só celsius é armazenado; fahrenheit é uma “visão” dele. O setter faz a conversão inversa, então as duas escalas nunca ficam dessincronizadas.
Exercício 7. Sem usar o decorador @property, crie a classe Produto com uma propriedade preco que recusa valores negativos. Use a função property().
Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Produto:
def __init__(self, preco):
self.preco = preco
def _get_preco(self):
return self._preco
def _set_preco(self, valor):
if valor < 0:
raise ValueError("Preço não pode ser negativo")
self._preco = valor
preco = property(_get_preco, _set_preco)
p = Produto(10)
p.preco = 25
print(p.preco)
try:
p.preco = -5
except ValueError as erro:
print(erro)
Saída:
1
2
25
Preço não pode ser negativo
property(_get_preco, _set_preco) faz o mesmo que @property + @preco.setter. O underline no nome dos métodos indica que eles são internos e que o jeito certo de usar é p.preco.
Exercício 8. Crie a classe Arquivo, que recebe uma lista de linhas, com o atributo total_palavras calculado uma única vez, mesmo que seja lido várias vezes. Imprima "contando..." dentro do cálculo para provar isso.
Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import cached_property
class Arquivo:
def __init__(self, linhas):
self.linhas = linhas
@cached_property
def total_palavras(self):
print("contando...")
return sum(len(linha.split()) for linha in self.linhas)
arq = Arquivo(["ola mundo", "python e legal"])
print(arq.total_palavras)
print(arq.total_palavras)
Saída:
1
2
3
contando...
5
5
"contando..." aparece uma vez só: na segunda leitura, cached_property devolve o valor guardado na instância. Com @property comum, a mensagem apareceria duas vezes.
Quer praticar orientação a objetos, decorators e o resto da linguagem construindo projetos guiados? Conheça a Jornada Python, nosso curso de Python completo.
Conclusão
Neste guia de @property, você aprendeu:
✅ Getters/Setters - Interface Pythônica sem .get_x()
✅ Validação - Dados sempre válidos automaticamente
✅ Atributos computados - Cálculos sob demanda (idade, temperatura)
✅ Read-only - Propriedades somente leitura
✅ cached_property - Cálculo feito uma vez e guardado
✅ @property vs públicos e vs dataclass - Quando usar cada um
Principais lições:
- @property torna métodos acessíveis como atributos
- Use para validação e cálculos
- Setter controla modificações e precisa ter o mesmo nome da property
-
Deleter permite
del objeto.atributo - Guarde o valor real em
self._nomepara evitarRecursionError - Property tem overhead mínimo (99% dos casos, use!)
Próximos passos:
- Pratique Classes e Objetos
- Explore Dataclasses (reduz boilerplate)
- Aprenda
__getattr__e__setattr__ - Estude descriptors (property avançado)
O decorador @property é uma ferramenta poderosa que nos permite transformar métodos em propriedades de uma classe, oferecendo um acesso mais intuitivo e controlado a essas propriedades.
Além de fornecer uma sintaxe mais elegante, as propriedades também nos permitem adicionar validações, conversões de dados e realizar cálculos adicionais antes de retornar ou definir o valor de um atributo.
Com esse conhecimento em mãos, você está pronto para utilizar o @property em suas classes Python e tornar seus códigos mais legíveis e eficientes.
Experimente utilizar propriedades em seus projetos e descubra como elas podem simplificar o acesso e a manipulação de atributos em suas classes.
É isso por hoje! Nos vemos no próximo artigo ![]()
Perguntas frequentes
O que é e para que serve o @property em Python?
@property é um decorador embutido do Python que transforma um método de uma classe em um atributo gerenciado. Você acessa obj.area sem parênteses e, por trás, o método é executado. Serve para criar atributos calculados (como área ou idade), atributos somente leitura e, junto com @area.setter, validar ou converter valores sempre que alguém atribui algo ao atributo, sem mudar a interface pública da classe.
Como criar getter e setter em Python com @property?
Crie o getter com @property em um método que devolve o valor guardado em um atributo interno, por convenção com underline (return self._nome). Depois crie o setter com o decorador @nome.setter em um método com o MESMO nome, recebendo o novo valor (def nome(self, valor): self._nome = valor). O uso fica obj.nome para ler e obj.nome = 'Ana' para escrever.
Como definir um setter para o atributo nome sem usar @property?
Use a função embutida property(), que é o que o decorador faz por baixo: defina os métodos get_nome(self) e set_nome(self, valor) na classe e, no corpo da classe, escreva nome = property(get_nome, set_nome). Assim obj.nome = 'Ana' chama set_nome. A alternativa sem property nenhuma é um método comum set_nome(self, valor) chamado explicitamente, estilo Java, que funciona mas não é o jeito idiomático em Python.
O que acontece ao atribuir um valor a uma propriedade que não tem setter?
O Python levanta AttributeError, porque uma property sem setter é somente leitura. A mensagem muda conforme a versão: no Python 3.10 é AttributeError: can't set attribute 'area' e do 3.11 em diante é AttributeError: property 'area' of 'Retangulo' object has no setter. Para permitir a atribuição, defina um método com o mesmo nome decorado com @area.setter.
Por que o setter da minha property causa RecursionError?
Porque dentro do setter você escreveu self.nome = valor em vez de self._nome = valor. Atribuir a self.nome chama o próprio setter de novo, que chama de novo, até estourar o limite com RecursionError: maximum recursion depth exceeded. O mesmo vale para o getter com return self.nome. Guarde o valor real em um atributo com outro nome, por convenção self._nome.
O que significa o @ antes de um método ou função em Python?
O @ antes de uma definição é a sintaxe de decorador: @property em cima de def area(self) equivale a escrever area = property(area) logo depois da função. O decorador recebe a função e devolve outra coisa no lugar dela, no caso um objeto property. Fora desse contexto, o @ entre dois valores (a @ b) é o operador de multiplicação de matrizes, usado por bibliotecas como NumPy.
Qual a diferença entre @property e functools.cached_property?
@property executa o método toda vez que o atributo é lido, então o valor está sempre atualizado. functools.cached_property (Python 3.8+) executa o método só na primeira leitura e guarda o resultado na instância; as próximas leituras devolvem o valor salvo. Use cached_property para cálculos caros sobre dados que não mudam, e apague o cache com del obj.atributo quando precisar recalcular.
Qual a diferença entre @property e @dataclass?
Eles resolvem problemas diferentes e podem ser usados juntos. @dataclass gera automaticamente __init__, __repr__ e __eq__ para uma classe que guarda dados, economizando código repetitivo. @property controla o acesso a UM atributo, calculando ou validando o valor a cada leitura ou escrita. Uma dataclass valida só na criação (via __post_init__); para validar a cada atribuição, ou para campos calculados, use @property.
"Porque o Senhor dá a sabedoria, e da sua boca vem a inteligência e o entendimento" Pv 2:6