Blog

@property en Python, en lugar de getters y setters

Todo lenguaje con campos privados te enseña a escribir get_x() y set_x() desde el principio, por si más adelante necesitas validación. Python no necesita eso, y vale más entender la razón que memorizarla.

La costumbre que Python no necesita

# 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())

Imprime 25. Seis líneas de ceremonia que no hacen nada salvo afear el lugar donde se llama.

Empieza con un atributo normal

# 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)

Imprime 25. El mismo comportamiento, sin ceremonia.

La objeción es evidente: ¿y qué pasa cuando necesites validación? En un lenguaje sin properties la respuesta es «cambias todas las llamadas», y por eso la gente escribe getters a la defensiva. En Python la respuesta es que no lo haces.

Agrega @property después, y afuera no cambia nada

# 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)

Imprime:

25
ValueError: -300 is below absolute zero

t3.celsius = 25 se sigue leyendo como una asignación de atributo porque, sintácticamente, lo es. La property la intercepta. Todas las llamadas que ya existían siguen funcionando sin cambios. Ese es todo el argumento: puedes posponer la decisión hasta tener un motivo, porque tomarla después no cuesta nada.

Fíjate que __init__ asigna self.celsius, no self._celsius. Eso hace que la construcción pase por el setter, así que un valor inválido se rechaza también al crear el objeto y no solo en una asignación posterior.

Properties calculadas

El otro uso, y el más común: un valor derivado de otros, que así nunca puede quedar desactualizado.

# 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)

Imprime:

212.0
32.0

Guarda fahrenheit como un atributo real y tendrás dos fuentes de verdad que se van a contradecir la primera vez que alguien cambie una de las dos. Calculada, no puede.

Solo lectura, gratis

Deja fuera el setter y la asignación falla:

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

Imprime:

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

Ese mensaje es de Python 3.11 en adelante. Las versiones anteriores dicen can't set attribute, que ayuda menos y significa lo mismo.

Qué recordar

  • Empieza con un atributo normal. Siempre.

  • Agrega @property cuando de verdad necesites validación o cálculo: quien llama nunca se entera.

  • Asigna a través de la property en __init__ para que la construcción también se valide.

  • Una property sin setter es de solo lectura, que es la forma más barata de proteger un valor derivado.

Lo único que no hay que hacer es envolver cada atributo en una property «por si acaso». Eso es la costumbre de los getters con mejor sintaxis, y cuesta la misma claridad a cambio de la misma 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.