W3docs

Python @property: Getters e Setters

Aprenda o decorador @property do Python: crie getters, setters, deleters e atributos calculados com sintaxe limpa e controle total de validação.

O decorador @property é o mecanismo embutido do Python para transformar um método em um atributo gerenciado. Em vez de escrever métodos get_x() e set_x() como em outras linguagens, você escreve um acesso a atributo de aparência normal (obj.x) mantendo controle total sobre o que acontece quando esse atributo é lido, escrito ou excluído.

Este capítulo aborda:

  • Por que properties existem e quando usá-las
  • Criando uma property somente leitura com @property
  • Adicionando um setter com @<name>.setter
  • Adicionando um deleter com @<name>.deleter
  • Properties calculadas (derivadas)
  • Atualizando um atributo simples para uma property sem quebrar o código existente
  • A função embutida property() — o mecanismo subjacente ao decorador
  • Como as properties funcionam como descritores (uma breve visão interna)
  • Armadilhas comuns

Antes de ler, certifique-se de estar familiarizado com classes e objetos em Python. Properties são uma ferramenta fundamental para o encapsulamento em Python. Para métodos de classe e estáticos, consulte @staticmethod e @classmethod.

Por Que as Properties Existem

Considere uma classe que armazena uma temperatura em Celsius. Uma implementação ingênua expõe o valor interno diretamente:

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

t = Temperature(25)
t.celsius = -5000   # nothing stops this — physically impossible

O problema: nada impede que os chamadores definam uma temperatura abaixo do zero absoluto (−273,15 °C). Você poderia adicionar um método set_celsius() com validação, mas os chamadores precisariam alterar seu código de t.celsius = 100 para t.set_celsius(100) — uma mudança de API incompatível.

@property resolve isso de forma elegante. Você mantém a sintaxe t.celsius = 100 enquanto adiciona uma camada de controle nos bastidores.

Getter Básico: Acesso Somente Leitura

O uso mais simples de @property é um atributo somente leitura respaldado por uma variável privada:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius   # store in a private attribute

    @property
    def celsius(self):
        return self._celsius

O decorador @property faz com que celsius pareça um atributo simples para o chamador:

t = Temperature(25)
print(t.celsius)   # 25  — no parentheses; Python calls the getter automatically

Como não há setter, tentar atribuir um valor levanta um erro:

t.celsius = 30
# AttributeError: property 'celsius' of 'Temperature' object has no setter

Esta é a forma correta de modelar um valor que deve ser definido apenas no momento da construção ou por meio de métodos específicos.

Adicionando um Setter com Validação

Decore um segundo método com @<property_name>.setter para lidar com escritas:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError('Temperature below absolute zero')
        self._celsius = value

Agora tanto a leitura quanto a escrita funcionam com a sintaxe de atributo simples:

t = Temperature(25)
print(t.celsius)   # 25

t.celsius = 100
print(t.celsius)   # 100

t.celsius = -300   # ValueError: Temperature below absolute zero

Regra fundamental: o setter e o getter devem ter o mesmo nome (celsius nos dois casos). O decorador @celsius.setter vincula o novo método ao objeto property celsius já existente.

Properties Calculadas

Uma property não precisa corresponder a um atributo armazenado. Ela pode calcular um valor dinamicamente a partir de outros dados:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError('Temperature below absolute zero')
        self._celsius = value

    @property
    def fahrenheit(self):
        return self._celsius * 9 / 5 + 32

fahrenheit não possui variável de apoio — ela deriva seu valor de _celsius sempre que é lida:

t = Temperature(0)
print(t.fahrenheit)   # 32.0

t.celsius = 100
print(t.fahrenheit)   # 212.0

Como não há @fahrenheit.setter, tentar escrever t.fahrenheit = 100 levanta um AttributeError. Properties calculadas são naturalmente somente leitura, a menos que você adicione explicitamente um setter.

Um exemplo real de property calculada

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height

    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        if value <= 0:
            raise ValueError('Width must be positive')
        self._width = value

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        if value <= 0:
            raise ValueError('Height must be positive')
        self._height = value

    @property
    def area(self):
        return self._width * self._height   # computed; no setter

    @property
    def perimeter(self):
        return 2 * (self._width + self._height)   # computed; no setter


r = Rectangle(4, 5)
print(r.area)       # 20
print(r.perimeter)  # 18

r.width = 10
print(r.area)       # 50

r.width = -1        # ValueError: Width must be positive

Adicionando um Deleter

O decorador @<property_name>.deleter permite executar código quando o chamador usa del obj.attr:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError('Temperature below absolute zero')
        self._celsius = value

    @celsius.deleter
    def celsius(self):
        print('Deleting celsius')
        del self._celsius


t = Temperature(25)
del t.celsius           # Deleting celsius
print(t.celsius)        # AttributeError: 'Temperature' object has no attribute '_celsius'

Deleters são usados com menos frequência do que getters e setters. Eles são úteis quando:

  • Remover um valor em cache para forçar o recálculo no próximo acesso.
  • Liberar explicitamente recursos vinculados a um atributo.
  • Garantir que, uma vez excluído, um valor não possa ser relido sem reatribuição.

Atualizando um Atributo Simples para uma Property

Um dos maiores benefícios práticos de @property é que você pode começar com um atributo público simples e adicionar validação posteriormente sem alterar nenhum código chamador. Isso é às vezes chamado de princípio de acesso uniforme.

# Version 1 — plain attribute, no validation
class Circle:
    def __init__(self, radius):
        self.radius = radius

c = Circle(5)
print(c.radius)   # 5
c.radius = 10     # works, but nothing stops c.radius = -1

Mais tarde você precisa de validação. Com @property você pode adicioná-la sem tocar nos chamadores:

# Version 2 — property with validation; public interface unchanged
import math

class Circle:
    def __init__(self, radius):
        self.radius = radius   # this now calls the setter

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError('Radius cannot be negative')
        self._radius = value

    @property
    def area(self):
        return math.pi * self._radius ** 2


c = Circle(5)
print(c.radius)          # 5
print(f'{c.area:.4f}')   # 78.5398

c.radius = 10
print(c.radius)          # 10

c.radius = -1            # ValueError: Radius cannot be negative

Todo código existente que lê ou escreve c.radius continua funcionando sem modificação.

A Função Embutida property()

@property é açúcar sintático para a função embutida property(). Estas duas definições são equivalentes:

# --- decorator style (recommended) ---
class Person:
    def __init__(self, age):
        self._age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int) or value < 0:
            raise ValueError('Age must be a non-negative integer')
        self._age = value
# --- property() style (explicit) ---
class Person:
    def __init__(self, age):
        self._age = age

    def _get_age(self):
        return self._age

    def _set_age(self, value):
        if not isinstance(value, int) or value < 0:
            raise ValueError('Age must be a non-negative integer')
        self._age = value

    def _del_age(self):
        del self._age

    age = property(_get_age, _set_age, _del_age, 'The person\'s age in years')

property(fget, fset, fdel, doc) aceita até quatro argumentos: uma função getter, uma função setter, uma função deleter e uma docstring. Qualquer um deles pode ser None.

p = Person(30)
print(p.age)               # 30
p.age = 31
print(p.age)               # 31
print(Person.age.__doc__)  # The person's age in years

A forma com decorador é mais limpa e é a recomendação padrão. A chamada explícita a property() é útil quando você quer passar a docstring sem um bloco de decorador multilinha, ou quando as funções de acesso já existem com outro nome.

Como as Properties Funcionam: Uma Breve Visão sobre Descritores

Internamente, property é um descritor — um objeto que define __get__, __set__ e __delete__ na classe. Quando Python busca obj.attr, ele verifica se o atributo na classe é um descritor e, em caso afirmativo, chama seu __get__ em vez de retornar o valor diretamente.

Você pode ver isso inspecionando o objeto property na classe:

class Square:
    def __init__(self, side):
        self._side = side

    @property
    def side(self):
        return self._side

    @side.setter
    def side(self, value):
        if value < 0:
            raise ValueError('Side must be non-negative')
        self._side = value


print(type(Square.side))    # <class 'property'>
print(Square.side.fget)     # <function Square.side at 0x...>
print(Square.side.fset)     # <function Square.side at 0x...>
print(Square.side.fdel)     # None

É por isso que ler Square.side retorna o próprio objeto property (descritor acessado na classe), enquanto ler s.side em uma instância aciona __get__ e retorna o inteiro. O protocolo de descritor é o mesmo mecanismo usado por classmethod, staticmethod e as próprias funções. Para um aprofundamento, consulte métodos mágicos do Python.

Armadilhas Comuns

Recursão infinita: esquecer o underscore

Um erro muito comum é usar o mesmo nome tanto para a property quanto para o atributo de apoio:

class Bad:
    @property
    def value(self):
        return self.value   # RecursionError! This calls the getter again

    @value.setter
    def value(self, v):
        self.value = v      # RecursionError! This calls the setter again

Sempre armazene o valor de apoio em um nome diferente, por convenção prefixado com um underscore:

class Good:
    @property
    def value(self):
        return self._value   # reads the private attribute

    @value.setter
    def value(self, v):
        self._value = v      # writes the private attribute

Setter definido antes do getter

O decorador setter @celsius.setter referencia o objeto property celsius, que deve existir primeiro. Sempre defina o getter (@property) antes do setter e do deleter no corpo da classe.

__init__ chama o setter automaticamente

Quando você escreve self.radius = radius dentro de __init__, Python chama o setter (se existir). Isso geralmente é o comportamento desejado — a validação também é executada no momento da construção. Mas isso significa que seu setter deve lidar com a atribuição inicial de forma adequada:

class Circle:
    def __init__(self, radius):
        self.radius = radius   # triggers the setter — validation applies here too

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError('Radius cannot be negative')
        self._radius = value

Circle(-1)   # ValueError: Radius cannot be negative

Properties são de nível de classe, não de instância

Você não pode adicionar uma property a uma única instância da forma como pode com atributos regulares. Properties são definidas na classe e se aplicam a todas as instâncias. Se você precisar de personalização de atributos por instância, consulte dataclasses do Python ou use uma abordagem baseada em __slots__.

Referência Rápida

SintaxeO que faz
@propertyDefine o getter; o atributo se torna somente leitura até que um setter seja adicionado
@<name>.setterDefine o setter; o atributo se torna legível e gravável
@<name>.deleterDefine o deleter; del obj.attr aciona este método
property(fget, fset, fdel, doc)Equivalente embutido sem a sintaxe de decorador
ClassName.prop.fgetA função getter subjacente
ClassName.prop.fsetA função setter subjacente (None se não houver setter)
ClassName.prop.fdelA função deleter subjacente (None se não houver deleter)

Prática

Prática
Which decorator do you use to define a setter for a property named `age`?
Which decorator do you use to define a setter for a property named `age`?
Prática
What happens when you assign to a property that has only a getter defined?
What happens when you assign to a property that has only a getter defined?
Prática
You have a plain public attribute `self.radius` in v1 of a class. In v2 you add a `@property` for `radius`. What happens to existing callers that write `obj.radius = 5`?
You have a plain public attribute `self.radius` in v1 of a class. In v2 you add a `@property` for `radius`. What happens to existing callers that write `obj.radius = 5`?
Was this page helpful?