Menu
Coddy logo textTech

Декораторы property: продвинутый уровень

Часть раздела Object Oriented Programming путешествия по Python на Coddy — урок 31 из 64.

Продвинутые декораторы свойств обеспечивают более сложный контроль над доступом к атрибутам, включая вычисляемые свойства, методы удаления и полное управление свойствами.

Вот пример вычисляемых свойств, которые получают значения из других атрибутов:

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)

Используйте вычисляемые свойства как обычные атрибуты:

rect = Rectangle(5, 3)
print(rect.area)      # 15 - вычисляется автоматически
print(rect.perimeter) # 16 - вычисляется автоматически

Создайте свойство с геттером, сеттером и делеттером:

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

Используйте полную функциональность свойств:

temp = Temperature()

Используйте сеттер с валидацией:

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

# temp.temperature = -300  # Вызовет ошибку ValueError

Используйте deleter:

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

Создайте более сложный пример с игровым счетом:

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}")  # Счет: 2500, Уровень: 3

del player.score
print(f"Score: {player.score}, Level: {player.level}")  # Счет: 0, Уровень: 1

Вывод:

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

Ключевой момент: Продвинутые декораторы свойств позволяют создавать вычисляемые свойства (рассчитываемые на основе других данных), удаление свойств с помощью @property.deleter и полный контроль над получением, установкой и удалением атрибутов. Это создает интуитивно понятные интерфейсы, сохраняя при этом строгую валидацию данных и инкапсуляцию.

challenge icon

Задание

Средне

В этом испытании вы реализуете класс Rectangle с правильной инкапсуляцией и валидацией свойств.

  • rectangle.py — это файл, который вам нужно отредактировать, он содержит комментарии TODO, которые помогут вам в реализации
  • driver.py — содержит обширные тестовые сценарии (не изменять)
  1. Реализуйте приватные атрибуты для _width и _height
  2. Создайте свойства для width и height с валидацией (значения должны быть положительными)
    • Вызывайте соответствующие сообщения ValueError, как указано в TODO
    • Важно: Проверяйте, если значение меньше или равно 0, затем вызывайте ошибку (это обеспечит согласованное поведение)
  3. Реализуйте свойства только для чтения для area и perimeter
  4. Создайте свойство dimensions с функциональностью геттера, сеттера и делетера, как описано в TODO
    • Важно: В сеттере используйте синтаксис распаковки кортежа: width, height = dimensions

Шпаргалка

Продвинутые декораторы свойств обеспечивают сложный контроль над доступом к атрибутам с помощью вычисляемых свойств, удалителей (deleters) и полного управления свойствами.

Вычисляемые свойства:

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)

Полное свойство с геттером, сеттером и удалителем:

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

Использование:

# Вычисляемые свойства
rect = Rectangle(5, 3)
print(rect.area)      # 15
print(rect.perimeter) # 16

# Полная функциональность свойств
temp = Temperature()
temp.temperature = 25  # Использует сеттер
print(temp.temperature)  # Использует геттер

del temp.temperature   # Использует удалитель

Попробуйте сами

from rectangle import Rectangle

# Обработчик тестовых случаев
test_case = input()

# Тест базовой функциональности
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}")
    
    # Тест свойства dimensions
    print(f"Dimensions: {rect.dimensions}")
    rect.dimensions = (10, 8)
    print(f"New area: {rect.area}")
    
    # Тест валидации
    try:
        rect.width = -2
    except ValueError as e:
        print(f"Validation error: {e}")
    
    # Тест удаления (deleter)
    del rect.dimensions
    print(f"After reset: {rect.dimensions}")

# Тест с нулевыми значениями
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}")

# Тест с отрицательными значениями
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}")

# Тест с большими значениями
elif test_case == "large_values":
    rect = Rectangle(1000000, 2000000)
    print(f"Large rectangle area: {rect.area}")
    print(f"Large rectangle perimeter: {rect.perimeter}")

# Тест со значениями с плавающей точкой
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}")

# Тест с ошибками типов
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  # Не кортеж
    except Exception as e:
        print(f"Type error setting dimensions: {type(e).__name__}: {e}")

# Тест нескольких операций со свойствами
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}")

# Тест граничных случаев валидации свойств
elif test_case == "validation_edge_cases":
    rect = Rectangle(5, 3)
    
    try:
        rect.width = 0
    except ValueError as e:
        print(f"Zero width error: {e}")
    
    # Очень маленькое положительное значение должно быть принято
    rect.height = 0.0001
    print(f"Small height accepted: {rect.height}")
    print(f"Area with small height: {rect.area}")

# Тест нескольких прямоугольников
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}")
    
    # Изменение каждого прямоугольника
    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}")

# Тест производительности с множеством операций
elif test_case == "performance_test":
    rect = Rectangle(5, 5)
    
    # Выполнение множества операций со свойствами
    for i in range(1000):
        rect.width = i % 10 + 1  # Значения 1-10
        rect.height = i % 5 + 1  # Значения 1-5
        area = rect.area  # Доступ к вычисляемому свойству
        perimeter = rect.perimeter  # Доступ к вычисляемому свойству
        dims = rect.dimensions  # Доступ к геттеру свойства
    
    print(f"Final state after 1000 operations:")
    print(f"Width: {rect.width}, Height: {rect.height}")
    print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming