Gyaan

Property Decorators

intermediate property getter setter encapsulation

In languages like Java, we write getName() and setName() methods to control access to attributes. Python says: forget that noise. We have @property — it lets us use regular attribute syntax while running custom logic behind the scenes.

The Problem

Say we want to validate an age before setting it. Without properties, we’d need getter/setter methods and everyone would have to remember to call them.

# The Java way — not Pythonic
class Person:
    def get_age(self):
        return self._age

    def set_age(self, value):
        if value < 0:
            raise ValueError("Age can't be negative")
        self._age = value

@property to the Rescue

With @property, we access age like a normal attribute, but Python secretly calls our getter/setter.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age  # this calls the setter!

    @property
    def age(self):  # getter
        return self._age

    @age.setter
    def age(self, value):  # setter
        if value < 0:
            raise ValueError("Age can't be negative")
        self._age = value

p = Person("Manish", 25)
print(p.age)      # 25 — calls the getter
p.age = 30        # calls the setter
p.age = -5        # ValueError: Age can't be negative

Notice we store the actual value in self._age (with underscore) but expose it as self.age. The underscore is a convention meaning “private, don’t touch directly.”

Read-Only Properties

If we only define the @property getter and skip the setter, the attribute becomes read-only.

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

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

c = Circle(5)
print(c.area)   # 78.53975
c.area = 100    # AttributeError: can't set attribute

This is great for computed properties — values derived from other attributes.

The Deleter

Less common, but we can also define what happens when someone uses del.

class Person:
    def __init__(self, name):
        self._name = name

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

    @name.setter
    def name(self, value):
        self._name = value

    @name.deleter
    def name(self):
        print("Deleting name...")
        self._name = None

p = Person("Manish")
del p.name  # Deleting name...

Validation in Setters

Properties really shine when we need to enforce rules. We can keep all validation in one place.

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

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

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Below absolute zero!")
        self._celsius = value

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

In simple language, @property lets us add logic to attribute access without changing how the outside world uses our class. No ugly get_x() calls — just clean obj.x syntax with validation and computation happening under the hood.