'How to apply multiple similar properties to a class
I am learning about the @property decorator at the moment. Consider a sample class below, named Rectangle. It has 2 attributes, width and height, both of which should be strictly positive integers. I enforced this using the @property decorator.
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
This code works perfectly well. Instantiation (Rectangle(-5,3)), direct assignment (rect.width = -5) and the setter methods (rect.set_width(-5)) raise a ValueError when the dimension is not a strictly positive number. However, the decoration of these width and height attributes is very similar. Is there a clean way to generate a @property decorator template for different attributes with the same conditions (e.g. positive integers)?
Bonus question: I tried by creating a PositiveInteger class. This indeed blocks instantiation (Rectangle(-5,3)) and the setter methods (rect.set_width(-5)) but incorrect direct assignment (rect.width = -5) remains possible. But why exactly is this not working?
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
Thanks in advance! P.S. I'm loving the Stack Overflow redesign.
Solution 1:[1]
My own answer After some extra days of Googling, I discovered descriptor classes in the Python documentation. Once instantiated in an external class, a descriptor class manages the getting and setting of attributes of the external class. This seems to work for instantiation, direct assignment and the setter methods. The only way unwanted changes can happen is if the private names of the attributes are accessed, e.g., 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
#==============================================================================
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Ben Zeen |
