AlgoInsight

Q&A

What is the @property in Python?

By AlgoInsight Team2026-06-18design-patternspython

Overview

Python's philosophy is to start with plain attributes and only add property logic when genuinely needed - no Java-style getters/setters for every field. When you do need controlled access (validation, computed values, backwards compatibility), @property upgrades a plain attribute into a managed one while keeping the call syntax clean.

graph TD
    A["@property → getter"] --> B["obj.name reads the value"]
    C["@name.setter → setter"] --> D["obj.name = value triggers validation"]
    E["@name.deleter → deleter"] --> F["del obj.name runs cleanup"]
    B & D & F --> G["All accessed like plain attributes - no ()"]
    style A fill:#1e1b4b,color:#fff

Read-Only Property (getter only)

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def area(self):
        """Computed on-the-fly - no stored area field."""
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.area)   # 78.53975 - called like an attribute, no parentheses
c.area = 99     # AttributeError: can't set attribute - read-only

This is a computed property: area is derived from radius and never stored. Because only the getter is defined, it's read-only.

Full Property with Getter + Setter + Validation

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius    # triggers the setter immediately

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError(f"Temperature below absolute zero: {value}")
        self._celsius = value   # store in a "private" backing variable

    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32

t = Temperature(100)
print(t.fahrenheit)     # 212.0
t.celsius = -300        # ValueError: Temperature below absolute zero

The setter runs whenever obj.celsius = ... is assigned - validation is automatic and centralized. The backing variable _celsius (convention: single underscore = "internal") avoids infinite recursion between the property and itself.

Deleter

    @celsius.deleter
    def celsius(self):
        print("Deleting temperature")
        del self._celsius

del t.celsius   # prints "Deleting temperature" and removes _celsius

Backwards Compatibility Pattern

The most important use case in practice: you start with a plain attribute and later need to add validation or computation without breaking all existing code that reads/writes obj.name:

# v1 - plain attribute
class User:
    def __init__(self, name):
        self.name = name    # direct attribute

# v2 - add validation; same public API
class User:
    def __init__(self, name):
        self.name = name    # this now calls the setter

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not value.strip():
            raise ValueError("Name cannot be blank")
        self._name = value.strip()

All existing code (user.name, user.name = "...") continues to work unchanged.

@property vs Plain Attribute

Why This Matters in Interviews

Python OOP interviews use @property to test encapsulation idioms. Key points: it turns a method into an attribute-style access (no ()), the getter/setter/deleter trio, validation in the setter, computed properties in the getter, and - the most important pattern - adding logic to a plain attribute without changing the public API (backwards compatibility). Knowing to use a backing _var to avoid infinite recursion, and explaining the Pythonic "don't add getters/setters preemptively" philosophy, signals maturity.

Common Pitfalls

References

More answers