You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用装饰器与property高效校验RectangularRoom类属性的类型及范围?

Efficient Ways to Add Type & Range Constraints for RectangularRoom Properties

Great question! The repetitive getter/setter code you’re using for width can definitely be streamlined when adding constraints to height and dirt_amount. Here are two clean, maintainable approaches to avoid redundant validation logic:

1. Use @property Decorators with Reusable Validation Functions

Instead of writing separate getter/setter methods for each property, use Python’s modern @property decorator syntax, and extract validation logic into a reusable decorator. This keeps your code DRY (Don’t Repeat Yourself) and easy to read.

Example Implementation:

import math
import random

# Reusable validation decorator for integer values with range constraints
def validate_int(min_val=None, max_val=None):
    def decorator(setter_func):
        def wrapper(self, value):
            # Check if value is an integer
            if not isinstance(value, int):
                raise TypeError("Value must be an integer")
            # Check minimum value constraint
            if min_val is not None and value < min_val:
                raise ValueError(f"Value must be greater than or equal to {min_val}")
            # Check maximum value constraint (if needed)
            if max_val is not None and value > max_val:
                raise ValueError(f"Value must be less than or equal to {max_val}")
            # Pass validated value to the original setter
            return setter_func(self, value)
        return wrapper
    return decorator

class RectangularRoom(object):
    """
    A RectangularRoom represents a rectangular region containing clean or dirty tiles.
    A room has a width and a height and contains (width * height) tiles.
    Each tile has some fixed amount of dirt. The tile is considered clean only when the amount of dirt on this tile is 0.
    """
    def __init__(self, width, height, dirt_amount):
        """
        Initializes a rectangular room with the specified width, height, and dirt_amount on each tile.
        width: an integer > 0
        height: an integer > 0
        dirt_amount: an integer >= 0
        """
        # Assignments here will trigger the property setters and validate inputs
        self.width = width
        self.height = height
        self.dirt_amount = dirt_amount
        
        tiles = [(w,h) for w in range(self.width) for h in range(self.height)]
        self.room = {tile: dirt_amount for tile in tiles}

    @property
    def width(self):
        return self._width

    @width.setter
    @validate_int(min_val=1)
    def width(self, value):
        self._width = value

    @property
    def height(self):
        return self._height

    @height.setter
    @validate_int(min_val=1)
    def height(self, value):
        self._height = value

    @property
    def dirt_amount(self):
        return self._dirt_amount

    @dirt_amount.setter
    @validate_int(min_val=0)
    def dirt_amount(self, value):
        self._dirt_amount = value

    def __str__(self):
        return str(self.room)

Why This Works:

  • The validate_int decorator encapsulates all common integer validation logic, so you don’t repeat type checks and range checks for each property.
  • Using @property decorators makes the code more readable and follows Python’s idiomatic style.
  • Assigning values in __init__ now automatically triggers the setter validation, ensuring your initial inputs are valid (something your original code didn’t do!).

2. Use a Custom Descriptor Class

For even more flexibility and reusability across multiple classes, you can create a descriptor that handles integer validation. Descriptors are a powerful Python feature for managing attribute access.

Example Implementation:

import math
import random

class IntProperty:
    def __init__(self, min_val=None, max_val=None):
        self.min_val = min_val
        self.max_val = max_val

    def __get__(self, instance, owner):
        # Retrieve the value from the instance's __dict__
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        # Validate type
        if not isinstance(value, int):
            raise TypeError("Value must be an integer")
        # Validate minimum value
        if self.min_val is not None and value < self.min_val:
            raise ValueError(f"Value must be >= {self.min_val}")
        # Validate maximum value
        if self.max_val is not None and value > self.max_val:
            raise ValueError(f"Value must be <= {self.max_val}")
        # Store the validated value
        instance.__dict__[self.name] = value

    def __set_name__(self, owner, name):
        # Automatically set the attribute name when the descriptor is assigned to a class
        self.name = name

class RectangularRoom(object):
    """
    A RectangularRoom represents a rectangular region containing clean or dirty tiles.
    A room has a width and a height and contains (width * height) tiles.
    Each tile has some fixed amount of dirt. The tile is considered clean only when the amount of dirt on this tile is 0.
    """
    # Define properties using the custom descriptor
    width = IntProperty(min_val=1)
    height = IntProperty(min_val=1)
    dirt_amount = IntProperty(min_val=0)

    def __init__(self, width, height, dirt_amount):
        """
        Initializes a rectangular room with the specified width, height, and dirt_amount on each tile.
        width: an integer > 0
        height: an integer > 0
        dirt_amount: an integer >= 0
        """
        self.width = width
        self.height = height
        self.dirt_amount = dirt_amount
        
        tiles = [(w,h) for w in range(self.width) for h in range(self.height)]
        self.room = {tile: dirt_amount for tile in tiles}

    def __str__(self):
        return str(self.room)

Why This Works:

  • The IntProperty descriptor encapsulates all validation logic in one place, making your RectangularRoom class extremely clean and focused on its core functionality.
  • Descriptors are reusable across multiple classes—if you ever need another class with integer properties, you can just import IntProperty and use it.
  • Like the decorator approach, assigning values in __init__ triggers validation, ensuring your initial state is valid.

Which Approach to Choose?

  • Use the decorator approach if you only need validation for this specific class, or if you want more flexibility to customize validation for individual properties.
  • Use the descriptor approach if you plan to reuse this validation logic across multiple classes, or if you want to keep your class definitions as clean as possible.

内容的提问来源于stack exchange,提问作者crystalizeee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:13:31