Menu
Coddy logo textTech

Décorateurs Property Avancés

Fait partie de la section Object Oriented Programming du Journey Python de Coddy — leçon 31 sur 64.

Les décorateurs de propriétés avancés offrent un contrôle plus sophistiqué sur l'accès aux attributs, y compris les propriétés calculées, les méthodes de suppression (deleters) et la gestion complète des propriétés.

Voici un exemple de propriétés calculées qui dérivent des valeurs d'autres attributs :

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)

Utilisez les propriétés calculées comme des attributs réguliers :

rect = Rectangle(5, 3)
print(rect.area)      # 15 - calculé automatiquement
print(rect.perimeter) # 16 - calculé automatiquement

Créez une propriété avec un getter, un setter et un 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

Utilisez la fonctionnalité complète de la propriété :

temp = Temperature()

Utilisez le setter avec validation :

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

# temp.temperature = -300  # Lèverait une ValueError

Utilisez le deleter :

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

Créez un exemple plus complexe avec un score de jeu :

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

Sortie :

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

Point clé : Les décorateurs de propriétés avancés permettent des propriétés calculées (calculées à partir d'autres données), la suppression de propriétés avec @property.deleter, et un contrôle total sur l'obtention, la définition et la suppression des attributs. Cela crée des interfaces intuitives tout en maintenant une validation des données et une encapsulation solides.

challenge icon

Défi

Moyen

Dans ce défi, vous allez implémenter une classe Rectangle avec une encapsulation appropriée et une validation des propriétés.

  • rectangle.py - C'est le fichier que vous devez éditer, contenant des commentaires TODO pour guider votre implémentation
  • driver.py - Contient des scénarios de test approfondis (ne pas modifier)
  1. Implémentez des attributs privés pour _width et _height
  2. Créez des propriétés pour width et height avec validation (doivent être positives)
    • Levez les messages ValueError appropriés comme spécifié dans les TODOs
    • Important : Vérifiez si la valeur est inférieure ou égale à 0, puis levez l'erreur (cela garantit un comportement cohérent)
  3. Implémentez des propriétés en lecture seule pour area et perimeter
  4. Créez une propriété dimensions avec les fonctionnalités getter, setter et deleter comme décrit dans les TODOs
    • Important : Dans le setter, utilisez la syntaxe de déballage de tuple : width, height = dimensions

Aide-mémoire

Les décorateurs de propriété avancés offrent un contrôle sophistiqué sur l'accès aux attributs avec des propriétés calculées, des suppresseurs (deleters) et une gestion complète des propriétés.

Propriétés calculées :

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)

Propriété complète avec Getter, Setter et 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

Utilisation :

# Propriétés calculées
rect = Rectangle(5, 3)
print(rect.area)      # 15
print(rect.perimeter) # 16

# Fonctionnalité de propriété complète
temp = Temperature()
temp.temperature = 25  # Utilise le setter
print(temp.temperature)  # Utilise le getter

del temp.temperature   # Utilise le deleter

Essayez vous-même

from rectangle import Rectangle

# Gestionnaire de cas de test
test_case = input()

# Test de fonctionnalité de base
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}")
    
    # Tester la propriété dimensions
    print(f"Dimensions: {rect.dimensions}")
    rect.dimensions = (10, 8)
    print(f"New area: {rect.area}")
    
    # Tester la validation
    try:
        rect.width = -2
    except ValueError as e:
        print(f"Validation error: {e}")
    
    # Tester le suppresseur (deleter)
    del rect.dimensions
    print(f"After reset: {rect.dimensions}")

# Test avec des valeurs nulles
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}")

# Test avec des valeurs négatives
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}")

# Test avec de grandes valeurs
elif test_case == "large_values":
    rect = Rectangle(1000000, 2000000)
    print(f"Large rectangle area: {rect.area}")
    print(f"Large rectangle perimeter: {rect.perimeter}")

# Test avec des valeurs à virgule flottante
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}")

# Test avec des erreurs de type
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  # Pas un tuple
    except Exception as e:
        print(f"Type error setting dimensions: {type(e).__name__}: {e}")

# Test d'opérations multiples sur les propriétés
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}")

# Test des cas limites de validation des propriétés
elif test_case == "validation_edge_cases":
    rect = Rectangle(5, 3)
    
    try:
        rect.width = 0
    except ValueError as e:
        print(f"Zero width error: {e}")
    
    # Une valeur positive très petite devrait être acceptée
    rect.height = 0.0001
    print(f"Small height accepted: {rect.height}")
    print(f"Area with small height: {rect.area}")

# Test de plusieurs rectangles
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}")
    
    # Modifier chaque rectangle
    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}")

# Test de performance avec de nombreuses opérations
elif test_case == "performance_test":
    rect = Rectangle(5, 5)
    
    # Effectuer de nombreuses opérations sur les propriétés
    for i in range(1000):
        rect.width = i % 10 + 1  # Valeurs 1-10
        rect.height = i % 5 + 1  # Valeurs 1-5
        area = rect.area  # Accès à la propriété calculée
        perimeter = rect.perimeter  # Accès à la propriété calculée
        dims = rect.dimensions  # Accès au getter de la propriété
    
    print(f"Final state after 1000 operations:")
    print(f"Width: {rect.width}, Height: {rect.height}")
    print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Object Oriented Programming