Menu
Coddy logo textTech

Property Decorators Avançados

Parte da seção Object Oriented Programming do Journey de Python da Coddy. Lição 31 de 64.

Decoradores de propriedade avançados fornecem um controle mais sofisticado sobre o acesso a atributos, incluindo propriedades computadas, deleters e gerenciamento completo de propriedades.

Aqui está um exemplo de propriedades computadas que derivam valores de outros 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)

Use propriedades computadas como atributos comuns:

rect = Rectangle(5, 3)
print(rect.area)      # 15 - calculado automaticamente
print(rect.perimeter) # 16 - calculado automaticamente

Crie uma propriedade com getter, setter e 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

Use a funcionalidade completa da propriedade:

temp = Temperature()

Use o setter com validação:

temp.temperature = 25
print(temp.temperature)  # 25

# temp.temperature = -300  # Levantaria ValueError

Use o deleter:

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

Crie um exemplo mais complexo com uma pontuação de jogo:

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}")  # Pontuação: 2500, Nível: 3

del player.score
print(f"Score: {player.score}, Level: {player.level}")  # Pontuação: 0, Nível: 1

Saída:

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

Ponto Chave: Decoradores de propriedade avançados permitem propriedades computadas (calculadas a partir de outros dados), exclusão de propriedades com @property.deleter e controle total sobre a obtenção, definição e exclusão de atributos. Isso cria interfaces intuitivas enquanto mantém uma forte validação de dados e encapsulamento.

challenge icon

Desafio

Médio

Neste desafio, você implementará uma classe Rectangle com encapsulamento adequado e validação de propriedades.

  • rectangle.py - Este é o arquivo que você precisa editar, contendo comentários TODO para guiar sua implementação
  • driver.py - Contém cenários de teste extensivos (não modifique)
  1. Implemente atributos privados para _width e _height
  2. Crie propriedades para width e height com validação (devem ser positivos)
    • Lance mensagens de ValueError apropriadas conforme especificado nos TODOs
    • Importante: Verifique se o valor é menor ou igual a 0 e, em seguida, lance o erro (isso garante um comportamento consistente)
  3. Implemente propriedades somente leitura para area e perimeter
  4. Crie uma propriedade dimensions com funcionalidades de getter, setter e deleter conforme descrito nos TODOs
    • Importante: No setter, use a sintaxe de desempacotamento de tupla: width, height = dimensions

Experimente você mesmo

from rectangle import Rectangle

# Manipulador de casos de teste
test_case = input()

# Teste de funcionalidade 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}")
    
    # Testar a propriedade dimensions
    print(f"Dimensions: {rect.dimensions}")
    rect.dimensions = (10, 8)
    print(f"New area: {rect.area}")
    
    # Testar validação
    try:
        rect.width = -2
    except ValueError as e:
        print(f"Validation error: {e}")
    
    # Testar deletador
    del rect.dimensions
    print(f"After reset: {rect.dimensions}")

# Teste com valores zero
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}")

# Teste com 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}")

# Teste com 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}")

# Teste com valores de ponto flutuante
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}")

# Teste com erros 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  # Não é uma tupla
    except Exception as e:
        print(f"Type error setting dimensions: {type(e).__name__}: {e}")

# Testar múltiplas operações de propriedade
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}")

# Testar casos extremos de validação de propriedade
elif test_case == "validation_edge_cases":
    rect = Rectangle(5, 3)
    
    try:
        rect.width = 0
    except ValueError as e:
        print(f"Zero width error: {e}")
    
    # Valor positivo muito pequeno deve ser aceito
    rect.height = 0.0001
    print(f"Small height accepted: {rect.height}")
    print(f"Area with small height: {rect.area}")

# Testar múltiplos retâ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 retâ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}")

# Testar desempenho com muitas operações
elif test_case == "performance_test":
    rect = Rectangle(5, 5)
    
    # Realizar muitas operações de propriedade
    for i in range(1000):
        rect.width = i % 10 + 1  # Valores 1-10
        rect.height = i % 5 + 1  # Valores 1-5
        area = rect.area  # Acessar propriedade computada
        perimeter = rect.perimeter  # Acessar propriedade computada
        dims = rect.dimensions  # Acessar getter de propriedade
    
    print(f"Final state after 1000 operations:")
    print(f"Width: {rect.width}, Height: {rect.height}")
    print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Object Oriented Programming

Pratique por conta própria: Compilador de Python online