Dominando el Decorador @property de Python: Getters y Setters Limpios y Compatibles
Dominando el Decorador @property de Python
Descubre cómo el decorador integrado @property de Python te permite escribir getters y setters concisos y mantenibles manteniendo tu API compatible hacia atrás.
El decorador @property de Python es una herramienta poderosa para encapsular acceso a atributos. Permite definir lógica personalizada que se ejecuta cada vez que un atributo se lee o escribe, sin forzar a los llamadores a cambiar su código. En las siguientes secciones recorreremos un ejemplo real —manejo de conversiones de temperatura—, mostraremos cómo imponer restricciones y explicaremos por qué @property es la solución idiomática.
Una Clase Sin Getters y Setters
Supongamos que necesitamos una clase simple que almacene temperatura en Celsius y pueda convertir a Fahrenheit:
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
Usarlo es directo:
# Crear un nuevo objeto
human = Celsius()
# Establecer la temperatura
human.temperature = 37
# Leer la temperatura
print(human.temperature)
# Convertir a Fahrenheit
print(human.to_fahrenheit())
Salida
37 98.60000000000001
El resultado de punto flotante es esperado debido a la representación binaria de decimales. Al inspeccionar human.__dict__, verás que Python almacena el valor en una entrada de diccionario plana:
>>> human.__dict__
{'temperature': 37}
Añadiendo Restricciones con Getters y Setters
Objetos del mundo real a menudo necesitan validación. Por ejemplo, temperaturas no pueden caer bajo cero absoluto (-273.15 °C). Una forma directa es ocultar el atributo y exponer métodos getter/setter explícitos:
class Celsius:
def __init__(self, temperature=0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
# getter
def get_temperature(self):
return self._temperature
# setter
def set_temperature(self, value):
if value < -273.15:
raise ValueError("Temperatura por debajo de -273.15 no es posible.")
self._temperature = value
Ahora la clase impone el límite inferior, pero cualquier código existente que accediera a obj.temperature debe actualizarse para llamar obj.get_temperature() y obj.set_temperature(val). Eso es una refactorización mayor para bases de código grandes.
Además, las variables "privadas" de Python son solo convención de nombres —_temperature aún puede accederse directamente, rompiendo encapsulación. Aquí es donde @property brilla.
Usando la Clase property
Python ofrece una clase property que une funciones getter y setter a un nombre de atributo:
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
# getter
def get_temperature(self):
print("Obteniendo valor...")
return self._temperature
# setter
def set_temperature(self, value):
print("Estableciendo valor...")
if value < -273.15:
raise ValueError("Temperatura por debajo de -273.15 no es posible")
self._temperature = value
# exponer como propiedad
temperature = property(get_temperature, set_temperature)
Con esta configuración, el siguiente código se comporta exactamente como la clase simple original, pero con validación tras bambalinas:
human = Celsius(37)
print(human.temperature) # dispara getter
print(human.to_fahrenheit()) # usa getter internamente
human.temperature = -300 # dispara setter y lanza ValueError
Salida
Estableciendo valor... Obteniendo valor... 37 Obteniendo valor... 98.60000000000001 Estableciendo valor... Traceback (most recent call last): ... ValueError: Temperatura por debajo de -273 no es posible
Observa cómo el código que accede a temperature permanece inalterado; solo la implementación subyacente ganó validación.
Consejo: Los datos reales viven en _temperature; temperature es solo una interfaz conveniente y segura.
El Decorador @property
Mientras la clase property funciona, la sintaxis de decorador de Python ofrece una forma más limpia y legible de lograr el mismo resultado. El decorador crea automáticamente el objeto property y adjunta el getter y setter al nombre del atributo.
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
@property
def temperature(self):
print("Obteniendo valor...")
return self._temperature
@temperature.setter
def temperature(self, value):
print("Estableciendo valor...")
if value < -273.15:
raise ValueError("Temperatura por debajo de -273 no es posible")
self._temperature = value
El uso permanece igual:
human = Celsius(37)
print(human.temperature)
print(human.to_fahrenheit())
# Intentar establecer temperatura inválida
try:
Celsius(-300)
except ValueError as e:
print(e)
Salida
Estableciendo valor... Obteniendo valor... 37 Obteniendo valor... 98.60000000000001 Estableciendo valor... Traceback (most recent call last): ... ValueError: Temperatura por debajo de -273 no es posible
Usar @property mantiene el espacio de nombres limpio, elimina la necesidad de nombres separados de métodos getter/setter, y es el patrón recomendado para la mayoría de desarrolladores Python.
python
- Dominando tipos de datos en Python: Guía práctica
- Dominando operadores en Python: Guía completa
- Dominando la sentencia pass en Python: Guía práctica
- Dominando argumentos de funciones en Python: Posicionales, por palabra clave y por defecto
- Dominando Diccionarios en Python: Creación, Manipulación y Técnicas Avanzadas
- Dominando la Herencia en Python: Conceptos, Sintaxis y Ejemplos Prácticos
- Dominando la Sobrecarga de Operadores en Python: Guía Práctica
- Dominando Iteradores en Python: Cómo Funcionan y Cómo Crear los Tuyos
- Explicación de Closures en Python: Cómo las Funciones Anidadas Capturan Variables
- Dominando el Decorador @property de Python: Getters y Setters Limpios y Compatibles
- Dominando datetime en Python: Guía Práctica de Fechas, Horas y Zonas Horarias