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:
¿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): ...
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:
Desventajas:
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:
Desventajas: