O que é e como utilizar @property no Python

Cansado de programar?

Conheça a melhor e mais completa formação de Python e Django e sinta-se um programador verdadeiramente competente. Além de Python e Django, você também vai aprender Banco de Dados, SQL, HTML, CSS, Javascript, Bootstrap e muito mais!

Quero aprender Python e Django de Verdade! Quero aprender!
Suporte

Tire suas dúvidas diretamente com o professor

Projetos práticos

Projetos práticos voltados para o mercado de trabalho

Prática profissional

Formação moderna com foco na prática profissional

Download do Artigo

✅ Artigo atualizado em Setembro 2026 Novas seções sobre setter sem decorador (função property()), propriedades somente leitura, cached_property e @property vs 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). Usar self.nome dentro do setter causa RecursionError.
  • Sem setter, a propriedade é somente leitura: atribuir um valor gera AttributeError.
  • Propriedade se acessa sem parênteses: obj.area() quebra com TypeError: 'int' object is not callable.
  • @property é atalho para a função property(fget, fset, fdel, doc); para calcular uma vez só e guardar, use functools.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 - AttributeError ao atribuir e cached_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…

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.

:bulb: 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? :thumbsup:

Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?

Sua assinatura não pôde ser validada.
Você fez sua assinatura com sucesso.

Assine as PyDicas e receba 30 dias do melhor conteúdo Python na sua Caixa de Entrada: direto e sem enrolação!

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 = 0 sobrescreve 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 @property para converter automaticamente tipos de dados. Por exemplo, podemos ter um atributo data que é armazenado como uma string e uma propriedade data que devolve o valor convertido em um objeto datetime.
  • Verificação de validade: podemos adicionar validações em um setter para garantir que os atributos estão dentro dos limites aceitáveis. Por exemplo, podemos ter um atributo idade que 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:

  • @dataclass age na classe inteira: gera __init__, __repr__ e __eq__ a partir das anotações de tipo, eliminando código repetitivo.
  • @property age 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._nome para evitar RecursionError
  • 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 :wink:

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.

Começe agora sua Jornada na Programação!

Não deixe para amanhã o sucesso que você pode começar a construir hoje!

#newsletter Olá :wave: Curtiu o artigo? Então faça parte da nossa Newsletter! Privacidade Não se preocupe, respeitamos sua privacidade. Você pode se descadastrar a qualquer momento.