Menu
CoddyTech

Decorator thuộc tính nâng cao

Thuộc phần Lập trình hướng đối tượng trong hành trình Python của Coddy. Bài học 31 trên 64.

Các decorator thuộc tính nâng cao cho phép kiểm soát việc truy cập thuộc tính tinh vi hơn, bao gồm thuộc tính được tính toán, hàm xóa và khả năng quản lý thuộc tính toàn diện.

Đây là ví dụ về các thuộc tính được tính toán, lấy giá trị từ những thuộc tính khác:

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)

Dùng các thuộc tính được tính toán như thuộc tính thông thường:

rect = Rectangle(5, 3)
print(rect.area)      # 15, tự động tính
print(rect.perimeter) # 16, tự động tính

Tạo một thuộc tính với getter, setter và 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

Sử dụng đầy đủ chức năng của thuộc tính:

temp = Temperature()

Dùng setter kèm kiểm tra tính hợp lệ:

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

# temp.temperature = -300  # Sẽ gây ra ValueError

Dùng hàm xóa:

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

Tạo một ví dụ phức tạp hơn với điểm số trong trò chơi:

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}")  # Điểm: 2500, cấp độ: 3

del player.score
print(f"Score: {player.score}, Level: {player.level}")  # Điểm: 0, cấp độ: 1

Đầu ra:

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

Điểm chính: Bộ trang trí thuộc tính (property decorator) nâng cao cho phép tạo thuộc tính tính toán (được tính từ dữ liệu khác), xóa thuộc tính bằng @property.deleter và kiểm soát hoàn toàn việc lấy, đặt và xóa thuộc tính. Cách này tạo ra giao diện trực quan, đồng thời duy trì khả năng xác thực dữ liệu chặt chẽ và tính đóng gói.

challenge icon

Thử thách

Trung bình

Trong thử thách này, bạn sẽ triển khai lớp Rectangle với tính đóng gói phù hợp và khả năng kiểm tra tính hợp lệ của thuộc tính.

  • rectangle.py - Đây là tệp bạn cần chỉnh sửa, trong đó có các chú thích TODO hướng dẫn cách triển khai
  • driver.py - Chứa nhiều tình huống kiểm thử (không chỉnh sửa)
  1. Lưu các giá trị trong những thuộc tính được bảo vệ (nội bộ) _width và _height
  2. Tạo thuộc tính cho width và height, có kiểm tra tính hợp lệ (giá trị phải là số dương)
    • Ném ngoại lệ ValueError phù hợp như mô tả trong các TODO
    • Quan trọng: Kiểm tra xem giá trị có nhỏ hơn hoặc bằng 0 hay không, rồi ném lỗi (điều này giúp đảm bảo hành vi nhất quán)
  3. Triển khai các thuộc tính chỉ đọc cho area và perimeter
  4. Tạo thuộc tính dimensions với chức năng getter, setter và deleter như mô tả trong các TODO
    • Quan trọng: Trong setter, hãy dùng cú pháp giải nén tuple: width, height = dimensions

Tự làm thử

from rectangle import Rectangle

# Xử lý test case
test_case = input()

# Kiểm tra chức năng cơ bản
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}")
    
    # Kiểm tra thuộc tính (property) dimensions
    print(f"Dimensions: {rect.dimensions}")
    rect.dimensions = (10, 8)
    print(f"New area: {rect.area}")
    
    # Kiểm tra việc xác thực
    try:
        rect.width = -2
    except ValueError as e:
        print(f"Validation error: {e}")
    
    # Kiểm tra thao tác xóa
    del rect.dimensions
    print(f"After reset: {rect.dimensions}")

# Kiểm tra với giá trị bằng 0
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}")

# Kiểm tra với giá trị âm
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}")

# Kiểm tra với giá trị lớn
elif test_case == "large_values":
    rect = Rectangle(1000000, 2000000)
    print(f"Large rectangle area: {rect.area}")
    print(f"Large rectangle perimeter: {rect.perimeter}")

# Kiểm tra với giá trị số thực
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}")

# Kiểm tra với lỗi kiểu dữ liệu
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  # Không phải tuple
    except Exception as e:
        print(f"Type error setting dimensions: {type(e).__name__}: {e}")

# Kiểm tra nhiều thao tác với thuộc tính
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}")

# Kiểm tra các trường hợp biên khi xác thực thuộc tính
elif test_case == "validation_edge_cases":
    rect = Rectangle(5, 3)
    
    try:
        rect.width = 0
    except ValueError as e:
        print(f"Zero width error: {e}")
    
    # Chấp nhận giá trị dương rất nhỏ
    rect.height = 0.0001
    print(f"Small height accepted: {rect.height}")
    print(f"Area with small height: {rect.area}")

# Kiểm tra nhiều hình chữ nhật
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}")
    
    # Thay đổi từng hình chữ nhật
    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}")

# Kiểm tra hiệu năng với nhiều thao tác
elif test_case == "performance_test":
    rect = Rectangle(5, 5)
    
    # Thực hiện nhiều thao tác với thuộc tính
    for i in range(1000):
        rect.width = i % 10 + 1  # Giá trị từ 1 đến 10
        rect.height = i % 5 + 1  # Giá trị từ 1 đến 5
        area = rect.area  # Truy cập thuộc tính được tính toán
        perimeter = rect.perimeter  # Truy cập thuộc tính được tính toán
        dims = rect.dimensions  # Truy cập phương thức getter của thuộc tính
    
    print(f"Final state after 1000 operations:")
    print(f"Width: {rect.width}, Height: {rect.height}")
    print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Python online