ProgramaciónDesarrollador Backend Python

¿Qué son los decoradores de propiedades de clases (@property) en Python, cómo funcionan y para qué sirven?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

El decorador @property permite convertir un método en un "atributo virtual" de la clase: para el usuario de la clase, parece una propiedad común, pero está controlada por la lógica de una función en Python. Originalmente, en Python, todos los atributos de una instancia eran accesibles directamente, pero se necesitó un mecanismo para gestionar el acceso a los datos sin cambiar la interfaz de la clase para soportar la encapsulación.

Historia:

En las primeras versiones de Python, no había un mecanismo explícito para restringir el acceso a los atributos. La tarea de encapsulación se resolvía a través de convenciones (por ejemplo, un guion bajo), pero cualquier cambio en la lógica de almacenamiento/validación de valores rompía la compatibilidad. Con la aparición del decorador @property, se tuvo la posibilidad de declarar un método como un atributo, dotarlo con un getter, un setter y un deleter, manteniendo la antigua interfaz de la clase.

Problema:

Cuando es necesario añadir lógica de validación o cálculo del valor de un campo posteriormente, reconstruir toda la interfaz resulta muy costoso, ya que se deben cambiar todos los lugares de acceso a la variable. Además, la visibilidad de la implementación interna del campo no debe revelarse al consumidor externo de la clase. @property permite virtualizar el acceso fácilmente.

Solución:

@property implementa el patrón "getter/setter con protección de interfaz": se puede añadir una propiedad calculada o controlar la asignación de un valor sin cambiar el código del cliente.

Ejemplo de código:

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("¡La temperatura está por debajo del cero absoluto!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 # Uso obj = Temperature(25) print(obj.celsius) # 25 print(obj.fahrenheit) # 77.0 obj.celsius = -300 # ValueError

Características clave:

  • Permite implementar propiedades calculadas, validadas o en caché sin cambiar la interfaz del cliente
  • Proporciona una interfaz de propiedad (sin paréntesis al acceder)
  • Permite controlar tanto la lectura como la escritura y eliminación del valor

Preguntas capciosas.

¿Siempre se puede añadir property a cualquier campo de la clase sin romper la compatibilidad?

No. Si el atributo era público y se utilizaba como una variable normal, el cambio a property puede ser transparente. Pero si en algún lugar se accedía a través de __dict__ o se realizaban comprobaciones de tipo mediante type(obj.attr), podría haber un comportamiento inesperado.

¿Se puede declarar solo un setter sin un getter para la propiedad?

No, primero es obligatorio el getter (método con @property), de lo contrario, Python no sabe a qué propiedad "vincular" el setter.

¿Cuál es el orden de los decoradores @property/@setter/@deleter y por qué?

Siempre se escribe primero @property (crea el objeto de propiedad), luego a través del nombre del método — @<name>.setter y @<name>.deleter (complementan la propiedad creada anteriormente).

class A: @property def value(self): ... @value.setter def value(self, v): ...

Errores comunes y anti-patrones

  • Acceso directo al atributo privado (por ejemplo, self._celsius) fuera de la clase
  • Violación del principio de encapsulación: lógica innecesaria en getter/setter
  • Re-definir property en herederos con conflicto de nombres

Ejemplo de la vida real

Caso negativo

Los campos de la clase eran públicos, luego se añadió una propiedad calculada a través de property después de lanzar la API pública. En el código antiguo, acceder al atributo cambia el comportamiento implícitamente o provoca un error.

Ventajas:

  • El usuario puede no cambiar los accesos al atributo

Desventajas:

  • Aparecen fallos inesperados en el código si la lógica del acceso se convierte en la lógica del setter/getter
  • Baja compatibilidad si el cliente utilizaba acceso directo a dict

Caso positivo

Desde el principio, al diseñar, los campos se definieron como privados (con un guion bajo) y los usuarios trabajaron solo a través de métodos/properties. La adición de nueva lógica a la propiedad en el futuro se realizó sin cambios en la interfaz del cliente.

Ventajas:

  • Encapsulación
  • Posibilidad de añadir fácilmente validación y cálculos

Desventajas:

  • Requiere disciplina en la documentación y el seguimiento de las convenciones; privacidad débil (un guion bajo no es suficiente para una protección total)