@property en Python: getters y setters sin métodos explícitos

El decorador @property permite que un método se comporte como un atributo: se accede sin paréntesis, pero por debajo ejecuta código. Esto te permite añadir validación, conversiones o cálculos sin cambiar la interfaz pública de la clase ni obligar a los usuarios a usar métodos get_algo() y set_algo() al estilo Java.

Getter básico con @property

class Circulo:
    def __init__(self, radio):
        self._radio = radio   # convención: _ indica uso interno

    @property
    def radio(self):
        return self._radio

    @property
    def area(self):           # propiedad computada (solo lectura)
        import math
        return math.pi * self._radio ** 2

    @property
    def diametro(self):
        return self._radio * 2

c = Circulo(5)
print(c.radio)     # 5      — acceso sin paréntesis
print(c.area)      # 78.53...
print(c.diametro)  # 10

# c.radio = 3     # AttributeError — no hay setter todavía

Setter con validación

class Temperatura:
    def __init__(self, celsius):
        self.celsius = celsius   # usa el setter desde __init__

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

    @celsius.setter
    def celsius(self, valor):
        if valor < -273.15:
            raise ValueError(f"Temperatura imposible: {valor}°C")
        self._celsius = valor

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

    @fahrenheit.setter
    def fahrenheit(self, valor):
        self.celsius = (valor - 32) * 5 / 9  # usa el setter de celsius

t = Temperatura(25)
print(t.fahrenheit)   # 77.0

t.fahrenheit = 98.6
print(t.celsius)      # 37.0

# t.celsius = -300    # ValueError: Temperatura imposible: -300°C

Deleter

class Cache:
    def __init__(self):
        self._datos = {}

    @property
    def datos(self):
        return self._datos.copy()

    @datos.setter
    def datos(self, nuevo):
        if not isinstance(nuevo, dict):
            raise TypeError("Se esperaba un diccionario")
        self._datos = nuevo

    @datos.deleter
    def datos(self):
        self._datos = {}
        print("Caché limpiado")

c = Cache()
c.datos = {"clave": "valor"}
print(c.datos)   # {'clave': 'valor'}
del c.datos      # Caché limpiado
print(c.datos)   # {}

Propiedades computadas en dataclass

from dataclasses import dataclass

@dataclass
class Rectangulo:
    ancho: float
    alto: float

    @property
    def area(self):
        return self.ancho * self.alto

    @property
    def perimetro(self):
        return 2 * (self.ancho + self.alto)

    @property
    def es_cuadrado(self):
        return self.ancho == self.alto

r = Rectangulo(4, 6)
print(r.area)        # 24
print(r.perimetro)   # 20
print(r.es_cuadrado) # False

El antipatrón a evitar

# MAL: getters y setters sin lógica, estilo Java
class Producto:
    def get_nombre(self):
        return self._nombre
    def set_nombre(self, valor):
        self._nombre = valor

# BIEN: acceso directo si no hay lógica; @property si hay validación
class Producto:
    def __init__(self, nombre):
        self.nombre = nombre   # acceso directo — simple y claro

Empieza con atributos públicos simples. Añade @property cuando necesites lógica de validación o computación sin romper la interfaz. Ese es exactamente el escenario para el que fue diseñado.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP