devnotes.

Properties and Encapsulation

The @property decorator lets a method be accessed like a plain attribute — no parentheses needed — enabling computed values and validated writes while keeping call sites clean.

class Circle:
    def __init__(self, radius):
        self._radius = radius        # single underscore: "internal, by convention"

    @property
    def radius(self):                  # accessed as circle.radius, no ()
        return self._radius

    @radius.setter
    def radius(self, value):            # runs when someone does circle.radius = value
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @property
    def area(self):                      # a read-only computed property, no setter defined
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.radius)      # 5 — looks like a plain attribute access
print(c.area)         # 78.53975 — computed on the fly, no () needed
c.radius = 10          # runs the setter, validates the new value
# c.radius = -5        # ❌ raises ValueError

Without @property, exposing validation on write would normally require explicit get_radius()/set_radius() methods (as in Java) — @property achieves the same validation while keeping the call site looking exactly like plain attribute access (c.radius, not c.get_radius()), which is more idiomatically Python.

A property with only a getter (like area above, with no @area.setter) is effectively read-only — attempting c.area = 100 raises an AttributeError. This pattern — a "computed" property with no backing field of its own — is common for values that are always derived from other attributes rather than stored directly.

Since Python has no true private attributes, _leading_underscore (convention: internal use) and __double_leading_underscore (triggers name mangling, making accidental access from outside the class harder, though still not impossible) are the two levels of "privacy" available, both relying on convention and discipline rather than compiler enforcement.

Properties and Encapsulation in Python | DevNotes