Historia del tema:
En la programación orientada a objetos clásica, la encapsulación se realiza a través de campos privados y getters/setters, lo que resulta engorroso y no "muy pythonic". En Python, a partir de la versión 2.2, apareció el decorador @property, que permite acceder a los métodos getters y setters como si fueran atributos normales, implementando una adecuada encapsulación con una sintaxis conveniente.
Problema:
Sin los decoradores de property, es necesario definir explícitamente los métodos para acceder y establecer un valor (por ejemplo, get_x() y set_x(val)), lo que hace que el código sea menos legible y los usuarios de la clase no estén protegidos contra el acceso directo a los datos internos. Surgen problemas durante la refactorización y al cambiar la lógica interna de almacenamiento o cálculo de valores.
Solución:
El decorador @property permite definir getters, setters y deleters utilizando una única sintaxis. Esto se ve conciso, es cómodo, encapsula los detalles de implementación y permite cambiar la forma de calcular una propiedad sin romper la interfaz de la clase.
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("¡Temperatura por debajo de -273.15°C no es posible!") self._celsius = value
Características clave:
@property permite acceder a los métodos como si fueran atributos normales.¿Se puede hacer una propiedad solo para lectura, pero no para escritura/eliminación?
Sí, si se define solo el método con el decorador @property sin un setter ni un deleter, la propiedad será accesible solo para lectura.
class Sample: @property def value(self): return 42
¿Qué pasa si el nombre de la propiedad coincide con un atributo privado?
Normalmente, la propiedad se usa como una "capa" para un atributo privado, cuyo nombre comienza con un guion bajo (por ejemplo, _x). Se debe evitar dicha coincidencia, de lo contrario, se producirá una llamada recursiva:
class Bad: @property def x(self): return self.x # Recursión infinita
¿Se puede asignar una property solo para la clase?
No, el @property estándar funciona con instancias de clase. Para crear una propiedad de clase, se deben usar patrones de terceros o bibliotecas especiales (el @classmethod junto con property no funcionan directamente).
Un desarrollador accede directamente al campo de la clase (self.celsius), en lugar de hacerlo a través de la property. Más tarde, se agrega validación, pero los consumidores de la clase aún pueden modificar directamente el atributo privado y eludir las comprobaciones.
Ventajas:
Es simple y rápido de trabajar, mientras no haya lógica compleja.
Desventajas:
Se rompe la encapsulación, fácil de obtener un estado incorrecto del objeto, surgen confusiones.
Uso de property y ocultación del atributo interno a través de _celsius. La validación, el almacenamiento en caché y la lógica se centralizan dentro de la property.
Ventajas:
El código está protegido, la interfaz es estable: se puede cambiar la implementación de la propiedad sin afectar a los usuarios de la clase.
Desventajas:
Con objetos grandes y complejos, se puede aumentar la complejidad de depuración si se abusa de las properties.