Decoradores de propiedades avanzados
Parte de la sección Object Oriented Programming del Journey de Python de Coddy. Lección 31 de 64.
Los decoradores de propiedades avanzados proporcionan un control más sofisticado sobre el acceso a los atributos, incluyendo propiedades calculadas, deleters y una gestión completa de las propiedades.
Aquí hay un ejemplo de propiedades calculadas que derivan valores de otros atributos:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
@property
def perimeter(self):
return 2 * (self.width + self.height)Usa las propiedades computadas como atributos regulares:
rect = Rectangle(5, 3)
print(rect.area) # 15 - calculado automáticamente
print(rect.perimeter) # 16 - calculado automáticamenteCrea una propiedad con getter, setter y deleter:
class Temperature:
def __init__(self):
self._temp = 0
@property
def temperature(self):
return self._temp
@temperature.setter
def temperature(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._temp = value
@temperature.deleter
def temperature(self):
print("Resetting temperature to 0")
self._temp = 0Utiliza la funcionalidad completa de la propiedad:
temp = Temperature()Usa el setter con validación:
temp.temperature = 25
print(temp.temperature) # 25
# temp.temperature = -300 # Lanzaría ValueErrorUsa el deleter:
del temp.temperature
print(temp.temperature) # 0Crea un ejemplo más complejo con la puntuación de un juego:
class Player:
def __init__(self, name):
self.name = name
self._score = 0
self._level = 1
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if value >= 0:
self._score = value
self._level = (value // 1000) + 1
else:
raise ValueError("Score cannot be negative")
@score.deleter
def score(self):
print(f"Resetting {self.name}'s progress")
self._score = 0
self._level = 1
@property
def level(self):
return self._level
player = Player("Alice")
player.score = 2500
print(f"Score: {player.score}, Level: {player.level}") # Score: 2500, Level: 3
del player.score
print(f"Score: {player.score}, Level: {player.level}") # Score: 0, Level: 1Salida:
15
16
25
Resetting temperature to 0
0
Score: 2500, Level: 3
Resetting Alice's progress
Score: 0, Level: 1Punto clave: Los decoradores de propiedades avanzados permiten propiedades calculadas (calculadas a partir de otros datos), la eliminación de propiedades con @property.deleter y un control total sobre la obtención, configuración y eliminación de atributos. Esto crea interfaces intuitivas mientras se mantiene una sólida validación de datos y encapsulación.
Desafío
IntermedioEn este desafío, implementarás una clase Rectangle con una encapsulación adecuada y validación de propiedades.
rectangle.py- Este es el archivo que necesitas editar, el cual contiene comentarios TODO para guiar tu implementacióndriver.py- Contiene escenarios de prueba extensos (no modificar)
- Implementa atributos privados para
_widthy_height - Crea propiedades para
widthyheightcon validación (deben ser positivos)- Lanza los mensajes ValueError apropiados como se especifica en los TODOs
- Importante: Verifica si el valor es menor o igual a 0, luego lanza el error (esto asegura un comportamiento consistente)
- Implementa propiedades de solo lectura para
areayperimeter - Crea una propiedad
dimensionscon funcionalidad de getter, setter y deleter como se describe en los TODOs- Importante: En el setter, usa la sintaxis de desempaquetado de tuplas:
width, height = dimensions
- Importante: En el setter, usa la sintaxis de desempaquetado de tuplas:
Pruébalo tú mismo
from rectangle import Rectangle
# Manejador de casos de prueba
test_case = input()
# Prueba de funcionalidad básica
if test_case == "default_test":
rect = Rectangle(5, 3)
print(f"Width: {rect.width}, Height: {rect.height}")
print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
# Probar la propiedad dimensions
print(f"Dimensions: {rect.dimensions}")
rect.dimensions = (10, 8)
print(f"New area: {rect.area}")
# Probar validación
try:
rect.width = -2
except ValueError as e:
print(f"Validation error: {e}")
# Probar eliminador (deleter)
del rect.dimensions
print(f"After reset: {rect.dimensions}")
# Prueba con valores cero
elif test_case == "zero_values":
try:
rect = Rectangle(0, 5)
except ValueError as e:
print(f"Error creating rectangle: {e}")
try:
rect = Rectangle(5, 0)
except ValueError as e:
print(f"Error creating rectangle: {e}")
# Prueba con valores negativos
elif test_case == "negative_values":
rect = Rectangle(5, 3)
original_dimensions = rect.dimensions
try:
rect.dimensions = (5, -3)
except ValueError as e:
print(f"Error setting dimensions: {e}")
print(f"Dimensions after failed update: {rect.dimensions}")
print(f"Original dimensions preserved: {rect.dimensions == original_dimensions}")
# Prueba con valores grandes
elif test_case == "large_values":
rect = Rectangle(1000000, 2000000)
print(f"Large rectangle area: {rect.area}")
print(f"Large rectangle perimeter: {rect.perimeter}")
# Prueba con valores de punto flotante
elif test_case == "float_values":
rect = Rectangle(3.5, 2.75)
print(f"Dimensions: {rect.dimensions}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
rect.dimensions = (1.1, 2.2)
print(f"New area with float dimensions: {rect.area}")
# Prueba con errores de tipo
elif test_case == "type_errors":
try:
rect = Rectangle("5", 3)
except Exception as e:
print(f"Type error during creation: {type(e).__name__}: {e}")
rect = Rectangle(5, 3)
try:
rect.dimensions = 10 # No es una tupla
except Exception as e:
print(f"Type error setting dimensions: {type(e).__name__}: {e}")
# Prueba de múltiples operaciones de propiedades
elif test_case == "property_operations":
rect = Rectangle(5, 10)
print(f"Initial - Width: {rect.width}, Height: {rect.height}, Area: {rect.area}")
rect.width = 8
print(f"After width change - Width: {rect.width}, Area: {rect.area}")
rect.height = 6
print(f"After height change - Height: {rect.height}, Area: {rect.area}")
rect.dimensions = (12, 9)
print(f"After dimensions change - Dimensions: {rect.dimensions}, Area: {rect.area}")
del rect.dimensions
print(f"After reset - Dimensions: {rect.dimensions}, Area: {rect.area}")
# Prueba de casos de borde en la validación de propiedades
elif test_case == "validation_edge_cases":
rect = Rectangle(5, 3)
try:
rect.width = 0
except ValueError as e:
print(f"Zero width error: {e}")
# Un valor positivo muy pequeño debería ser aceptado
rect.height = 0.0001
print(f"Small height accepted: {rect.height}")
print(f"Area with small height: {rect.area}")
# Prueba con múltiples rectángulos
elif test_case == "multiple_rectangles":
rect1 = Rectangle(5, 3)
rect2 = Rectangle(10, 2)
rect3 = Rectangle(4, 4)
print(f"Rectangle 1 - Area: {rect1.area}, Perimeter: {rect1.perimeter}")
print(f"Rectangle 2 - Area: {rect2.area}, Perimeter: {rect2.perimeter}")
print(f"Rectangle 3 - Area: {rect3.area}, Perimeter: {rect3.perimeter}")
# Modificar cada rectángulo
rect1.width = 7
rect2.height = 5
rect3.dimensions = (6, 6)
print(f"After modifications:")
print(f"Rectangle 1 - Dimensions: {rect1.dimensions}")
print(f"Rectangle 2 - Dimensions: {rect2.dimensions}")
print(f"Rectangle 3 - Dimensions: {rect3.dimensions}")
# Prueba de rendimiento con muchas operaciones
elif test_case == "performance_test":
rect = Rectangle(5, 5)
# Realizar muchas operaciones de propiedades
for i in range(1000):
rect.width = i % 10 + 1 # Valores 1-10
rect.height = i % 5 + 1 # Valores 1-5
area = rect.area # Acceder a propiedad calculada
perimeter = rect.perimeter # Acceder a propiedad calculada
dims = rect.dimensions # Acceder al getter de la propiedad
print(f"Final state after 1000 operations:")
print(f"Width: {rect.width}, Height: {rect.height}")
print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Fundamentos de la POO
Archivos externosIntroducción a la POOClases vs ObjetosEl parámetro selfMétodosAtributosMétodo constructor (__init__)Resumen - Calculadora simple4Herencia
Herencia básicaLa función super()Sobrescritura de métodosHerencia múltipleOrden de resolución de métodosResumen - Jerarquía de empleados7Métodos especiales
Introducción a los métodos mágicosSobrecarga de operadoresMétodos mágicos de contenedoresRepaso - Lista personalizada10Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy2Decoradores
Introducción a los decoradoresDecorador propertyDecorador de métodos estáticosDecorador de métodos de clase5Polimorfismo
Sobreescritura de métodos revisitadaDuck TypingClases y métodos abstractosDiseño de interfacesResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaMixinsMétodos estáticos y de claseDecoradores de claseGestores de contexto11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Propiedades de clase
Variables de instancia vs de claseDecoradores de propiedadesAtributos privadosResumen - Gestor de cuentas bancarias6Encapsulamiento
Miembros públicos, protegidos y privadosModificadores de accesoOcultamiento de informaciónDecoradores de propiedades avanzadosResumen - Sistema de registros de estudiantesPractica por tu cuenta: Compilador de Python online