如何将多个相似属性应用于 class

How to apply multiple similar properties to a class

我目前正在学习 @property 装饰器。考虑下面的示例 class,名为 Rectangle。它有 2 个属性,widthheight,它们都应该是严格的正整数。我使用 @property 装饰器强制执行此操作。

class Rectangle:
    '''
    A simple class to model a rectangle.

    Parameters
    ----------
    width : int
        The width of the rectangle.
    height : int
        The height of the rectangle.

    Returns
    -------
    None.
    '''
    
    #---INITIALIZATION---------------------------------------------------------

    def __init__(self, width : int, height : int):
        self.width = width
        self.height = height
    
    #---PROPERTIES-------------------------------------------------------------
    
    @property
    def width(self):
        return self._width
    @width.setter
    def width(self, val : int):
        if isinstance(val, int) and val > 0:
            self._width = val
        else:
            raise ValueError('Width cannot be negative.')
    @width.deleter
    def width(self):
        del self._width

    @property
    def height(self):
        return self._height
    @height.setter
    def height(self, val : int):
        if isinstance(val, int) and val > 0:
            self._height = val
        else:
            raise ValueError('Height cannot be negative.')
    @height.deleter
    def height(self):
        del self._height    
    
    #---SETTERS----------------------------------------------------------------
    
    def set_width(self, val : int):
        '''
        Set the Rectangle width to any stricly positive integer value.
        '''
        self.width = val
            
    def set_height(self, val):
        '''
        Set the Rectangle height to any stricly positive integer value.
        '''
        self.height = val

此代码运行良好。实例化 (Rectangle(-5,3))、直接赋值 (rect.width = -5) 和 setter 方法 (rect.set_width(-5)) 在维度不是严格正数时引发 ValueError。但是,widthheight这两个属性的修饰非常相似。 有没有一种干净的方法可以为具有相同条件(例如正整数)的不同属性生成 @property 装饰器模板?

奖励问题:我尝试创建一个 PositiveInteger class。这确实会阻止实例化 (Rectangle(-5,3)) 和 setter 方法 (rect.set_width(-5)),但不正确的直接赋值 (rect.width = -5) 仍然是可能的。 但为什么这不起作用?

class PositiveInteger:
    '''
    A simple class to decorate an attribute with an @property decorator to
    enforce strictly positive values.

    Parameters
    ----------
    name : str
        The name of the attribute.
    value : int
        The set value of the attribute.

    Returns
    -------
    None.
    '''
    
    #---INITIALIZATION---------------------------------------------------------

    def __init__(self, name : str, value : int):
        self.name = name
        self.value = value
        
    #---PROPERTIES-------------------------------------------------------------
    
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, val : int):
        if isinstance(val, int) and val > 0:
            self._value = val
        else:
            raise ValueError(f'{self.name.capitalize()} cannot be negative.')
    @value.deleter
    def value(self):
        del self._value

#==============================================================================

class Rectangle:
    '''
    A simple class to model a rectangle.

    Parameters
    ----------
    width : int
        The width of the rectangle.
    height : int
        The height of the rectangle.

    Returns
    -------
    None.
    '''
    
    #---INITIALIZATION---------------------------------------------------------

    def __init__(self, width : int, height : int):
        self.width = PositiveInteger('width', width).value
        self.height = PositiveInteger('height', height).value
                
    #---SETTERS----------------------------------------------------------------
    
    def set_width(self, val : int):
        '''
        Set the Rectangle width to any stricly positive integer value.
        '''
        self.width = PositiveInteger('width', val).value
            
    def set_height(self, val):
        '''
        Set the Rectangle height to any stricly positive integer value.
        '''
        self.height = PositiveInteger('height', val).value

提前致谢! P.S。我喜欢 Stack Overflow 的重新设计。

我自己的回答 经过多天的谷歌搜索,我在 Python documentation 中发现了描述符 classes。一旦在外部 class 中实例化,描述符 class 就会管理外部 class 属性的获取和设置。这似乎适用于实例化、直接赋值和 setter 方法。发生不需要的更改的唯一方法是访问属性的私有名称,例如 rect._height = -5.

#==============================================================================

class PositiveIntegerDescriptor: 
    '''
    A descriptor class to enforce an attribute to be a strictly positive integer.

    Parameters
    ----------
    name : str
        The name of the attribute.

    Returns
    -------
    None.
    '''
    
    #--INITIALIZATION----------------------------------------------------------
    
    def __init__(self, name : str):
        self.public_name = name
        self.private_name = '_' + name
            
    #--GETTERS AND SETTERS-----------------------------------------------------

    def __get__(self, obj, objtype=None):
        return getattr(obj, self.private_name)
    
    def __set__(self, obj, new_value):
        if isinstance(new_value, int) and new_value > 0:
            setattr(obj, self.private_name, new_value)
        else:
            raise ValueError(f'{self.public_name.capitalize()} must be a strictly positive integer.')

#==============================================================================

class Rectangle:
    '''
    A simple class to model a rectangle.

    Parameters
    ----------
    width : int
        The width of the rectangle.
    height : int
        The height of the rectangle.

    Returns
    -------
    None.
    '''
    
    #---DESCRIPTORS------------------------------------------------------------
    
    width = PositiveIntegerDescriptor('width')
    height = PositiveIntegerDescriptor('height')
    
    #---INITIALIZATION---------------------------------------------------------

    def __init__(self, width : int, height : int):
        self.width  = width
        self.height = height
   
    #---SETTERS----------------------------------------------------------------
    
    def set_width(self, value : int):
        '''
        Set the Rectangle width to any stricly positive integer value.
        '''
        self.width = value
            
    def set_height(self, value):
        '''
        Set the Rectangle height to any stricly positive integer value.
        '''
        self.height = value
    
#==============================================================================