What are properties (`@property`), and when should you use them over public attributes?
Quick Answer
`@property` turns a method into an attribute-like accessor, letting you run code on `get`/`set`/`delete` while keeping the call site looking like plain attribute access (`obj.value`, not `obj.get_value()`). Use it to add validation, computed/derived values, or lazy evaluation **without breaking the public API** — you can start with a plain attribute and convert it to a property later without touching any calling code.
Detailed Answer
Basic usage
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("radius must be positive")
self._radius = value
@property
def area(self): # read-only, computed property
return 3.14159 * self._radius ** 2
c = Circle(5)
c.radius # 25 -> looks like an attribute, actually calls the getter
c.radius = 10 # calls the setter, validates
c.area # computed on the fly, no setter defined -> read-only
c.area = 100 # AttributeError: can't set attribute
Callers write c.radius, not c.get_radius() — the property mechanism is
invisible from the outside.
Why this beats Java/C#-style getters/setters written from day one
Python convention starts with a plain public attribute
(self.radius = radius, no property at all). If validation or computed
logic is needed later, converting it to a @property doesn't change the
public API — obj.radius still works exactly the same for every existing
caller. Writing get_radius()/set_radius() methods from the start (the
Java convention) locks callers into method-call syntax even when no
validation is needed, and later can't be un-done without breaking callers.
Read-only vs computed properties
A property with only a getter (like area above) is effectively
read-only — attempting c.area = 100 raises AttributeError. This is a
clean way to expose a derived value without letting callers set it
directly and risk it going out of sync with radius.
@property vs __slots__ and descriptors
property is itself implemented as a descriptor (it defines
__get__/__set__/__delete__) — it's the built-in, most common special
case of the more general descriptor protocol used for custom
attribute-access behavior (see also: descriptors question).
Interview-ready summary: @property lets an attribute-looking access
run arbitrary code (validation, computed values, logging) while keeping
the calling code's syntax unchanged. The idiomatic Python pattern is to
start with plain attributes and only introduce a property when you
actually need that behavior — never write boilerplate getters/setters
preemptively.