property is a built-in descriptor for getter/setter. Custom descriptors offer more control.
1# property — simple getter/setter2class Circle:3 def __init__(self, radius):4 self._radius = radius56 @property7 def radius(self):8 return self._radius910 @radius.setter11 def radius(self, value):12 if value < 0:13 raise ValueError("Radius must be positive")14 self._radius = value1516 @property17 def area(self):18 return 3.14 * self._radius ** 21920# Custom descriptor — reusable validation21class Validated:22 def __init__(self, min_value=None, max_value=None):23 self.min_value = min_value24 self.max_value = max_value2526 def __set_name__(self, owner, name):27 self.name = name2829 def __get__(self, obj, objtype=None):30 if obj is None:31 return self32 return obj.__dict__.get(self.name)3334 def __set__(self, obj, value):35 if self.min_value is not None and value < self.min_value:36 raise ValueError(f"{self.name} must be >= {self.min_value}")37 if self.max_value is not None and value > self.max_value:38 raise ValueError(f"{self.name} must be <= {self.max_value}")39 obj.__dict__[self.name] = value4041class Product:42 price = Validated(min_value=0)43 quantity = Validated(min_value=0, max_value=1000)4445p = Product()46p.price = 100 # OK47# p.price = -10 # ValueError!
When to use:
@property: simple getter/setter.