Blog

@property em Python, no lugar de getters e setters

Toda linguagem com campos privados te ensina a escrever get_x() e set_x() desde o início, caso você precise de validação depois. Python não precisa disso, e vale entender o motivo em vez de decorar.

O hábito de que o Python não precisa

# the Java habit, which Python does not need
class TemperatureJava:
    def __init__(self, celsius):
        self._celsius = celsius
    def get_celsius(self):
        return self._celsius
    def set_celsius(self, value):
        self._celsius = value

t = TemperatureJava(20)
t.set_celsius(25)
print(t.get_celsius())

Ele imprime 25. Seis linhas de cerimônia que não fazem nada além de deixar as chamadas mais feias.

Comece com um atributo comum

# in Python you start with a plain attribute
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

t2 = Temperature(20)
t2.celsius = 25
print(t2.celsius)

Ele imprime 25. Mesmo comportamento, sem cerimônia.

A objeção é óbvia: e quando você precisar de validação? Numa linguagem sem properties a resposta é “você muda todas as chamadas”, e é por isso que as pessoas escrevem getters por precaução. Em Python a resposta é que você não muda.

Acrescente @property depois, e nada de fora muda

# and add @property later, without changing a single caller
class Temperature2:
    def __init__(self, celsius):
        self.celsius = celsius          # goes through the setter below

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

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError(f"{value} is below absolute zero")
        self._celsius = value

t3 = Temperature2(20)
t3.celsius = 25
print(t3.celsius)
try:
    t3.celsius = -300
except ValueError as err:
    print(type(err).__name__ + ':', err)

Ele imprime:

25
ValueError: -300 is below absolute zero

t3.celsius = 25 continua parecendo uma atribuição de atributo porque, sintaticamente, é uma. A property intercepta. Todas as chamadas que já existiam continuam funcionando sem mudança. É esse o argumento inteiro: você pode adiar a decisão até ter um motivo, porque tomá-la depois não custa nada.

Repare que o __init__ atribui self.celsius, não self._celsius. Isso faz a construção passar pelo setter, então um valor inválido é recusado na criação também, e não só numa atribuição posterior.

Properties calculadas

O outro uso, e o mais comum: um valor derivado de outros, que por isso nunca fica desatualizado.

# a computed property: derived, never stored, never stale
class Temperature3:
    def __init__(self, celsius):
        self.celsius = celsius
    @property
    def fahrenheit(self):
        return self.celsius * 9 / 5 + 32

t4 = Temperature3(100)
print(t4.fahrenheit)
t4.celsius = 0
print(t4.fahrenheit)

Ele imprime:

212.0
32.0

Guarde fahrenheit como atributo de verdade e você tem duas fontes de verdade que vão discordar na primeira vez que alguém mudar uma delas. Calculada, ela não consegue.

Somente leitura de graça

Deixe o setter de fora e a atribuição falha:

try:
    t4.fahrenheit = 50
except AttributeError as err:
    print(type(err).__name__ + ':', err)

Ele imprime:

AttributeError: property 'fahrenheit' of 'Temperature3' object has no setter

Essa mensagem é do Python 3.11 em diante. Versões mais antigas dizem can't set attribute, que ajuda menos e é a mesma coisa.

O que lembrar

  • Comece com um atributo comum. Sempre.

  • Acrescente @property quando realmente precisar de validação ou cálculo — quem chama nem percebe.

  • Atribua pela property no __init__ para que a construção também seja validada.

  • Uma property sem setter é somente leitura, que é o jeito mais barato de proteger um valor derivado.

A única coisa a não fazer é embrulhar todo atributo numa property “por precaução”. Isso é o hábito dos getters com sintaxe melhor, e custa a mesma clareza pelo mesmo nada.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.