@property lets a method look like a normal attribute. You read c.area without brackets, and Python runs a method to get the value. With a setter, you can also run code when someone assigns a new value, for example to reject a wrong one.
The program
python
import math class Circle: def __init__(self, radius): self.radius = radius # goes through the setter below @property def radius(self): return self._radius @radius.setter def radius(self, value): if value < 0: raise ValueError("radius cannot be negative") self._radius = value @property def area(self): return math.pi * self._radius ** 2 c = Circle(5) print(f"radius {c.radius}, area {c.area:.2f}") c.radius = 10 print(f"radius {c.radius}, area {c.area:.2f}") try: c.radius = -3 except ValueError as e: print("Error:", e) print("radius is still", c.radius)
output
radius 5, area 78.54 radius 10, area 314.16 Error: radius cannot be negative radius is still 10
How it works
- The real value is stored in
self._radius. The underscore at the start tells other programmers "this is internal, useradiusinstead". @radius.setterruns on every assignment toc.radius, including the one in__init__. SoCircle(-1)is rejected too.areahas no setter, so it is read-only. It is calculated every time you read it, so it is always correct for the current radius. Ifareawere a normal attribute set in__init__, it would keep the old value afterc.radius = 10.math.piis more accurate than typing 3.14159.
Read-only attributes
Assigning to a property that has no setter raises AttributeError:
python
import math class Circle: def __init__(self, radius): self.radius = radius @property def area(self): return math.pi * self.radius ** 2 c = Circle(2) try: c.area = 100 except AttributeError as e: print("Error:", e)
output
Error: property 'area' of 'Circle' object has no setter