Menu
Coddy logo textTech

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áticamente

Crea 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 = 0

Utiliza 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 ValueError

Usa el deleter:

del temp.temperature
print(temp.temperature)  # 0

Crea 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: 1

Salida:

15
16
25
Resetting temperature to 0
0
Score: 2500, Level: 3
Resetting Alice's progress
Score: 0, Level: 1

Punto 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.

challenge icon

Desafío

Intermedio

En 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ón
  • driver.py - Contiene escenarios de prueba extensos (no modificar)
  1. Implementa atributos privados para _width y _height
  2. Crea propiedades para width y height con 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)
  3. Implementa propiedades de solo lectura para area y perimeter
  4. Crea una propiedad dimensions con 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

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}")
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming

Practica por tu cuenta: Compilador de Python online