Property Decorators in Python

Property decorators in Python provide a way to define managed attributes in a class. The @property decorator allows us to define methods that act like attributes while implementing getter, setter, and deleter functionality. This helps in data encapsulation and validation.

Syntax

</>
Copy
class ClassName:
    @property
    def attribute_name(self):
        return self._attribute_name

    @attribute_name.setter
    def attribute_name(self, value):
        self._attribute_name = value

    @attribute_name.deleter
    def attribute_name(self):
        del self._attribute_name

How Property Decorators Work

The @property decorator converts a method into a getter. This allows us to access it like an attribute without using parentheses. The @attribute_name.setter decorator allows modifying the attribute, and @attribute_name.deleter allows deleting it.


Examples

1. Using @property to Define a Read-Only Attribute

In this example, we define a class Circle with a read-only attribute area. The value is calculated dynamically whenever it is accessed.

</>
Copy
import math

class Circle:
    def __init__(self, radius):
        self._radius = radius  # Private variable to store radius

    @property
    def area(self):
        """Computes the area of the circle dynamically."""
        return math.pi * self._radius ** 2

# Creating an instance of Circle
c = Circle(5)

# Accessing the area property
print("Circle area:", c.area)

# Trying to modify area (This will cause an AttributeError)
# c.area = 100  # Uncommenting this line will raise an error

Output:

Circle area: 78.53981633974483

Here, we used @property to define area as a read-only attribute. Trying to assign a new value to area would result in an error because we have not defined a setter.

2. Using @property with a Setter

Here, we define a Person class where the age attribute has both a getter and a setter. The setter ensures that the value assigned to age is valid.

</>
Copy
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age  # Private variable to store age

    @property
    def age(self):
        """Gets the age value."""
        return self._age

    @age.setter
    def age(self, value):
        """Sets the age value, ensuring it is a positive number."""
        if value < 0:
            raise ValueError("Age cannot be negative.")
        self._age = value

# Creating an instance of Person
p = Person("Arjun", 25)

# Accessing age property
print("Person's age:", p.age)

# Updating age
p.age = 30
print("Updated age:", p.age)

# Trying to assign a negative age (This will raise a ValueError)
# p.age = -5  # Uncommenting this line will raise an error

Output:

Person's age: 25
Updated age: 30

Here, the @age.setter ensures that the age cannot be negative. Trying to set a negative value results in an error.

3. Using @property with a Deleter

We add a deleter to the Person class, allowing us to delete the age attribute.

</>
Copy
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age  # Private variable to store age

    @property
    def age(self):
        """Gets the age value."""
        return self._age

    @age.setter
    def age(self, value):
        """Ensures age is positive before assigning."""
        if value < 0:
            raise ValueError("Age cannot be negative.")
        self._age = value

    @age.deleter
    def age(self):
        """Deletes the age attribute."""
        print("Deleting age...")
        del self._age

# Creating an instance of Person
p = Person("Arjun", 25)

# Deleting age attribute
del p.age

# Trying to access age after deletion (This will raise an AttributeError)
# print(p.age)  # Uncommenting this line will raise an error

Output:

Deleting age...

When del p.age is called, the deleter function runs and removes the _age attribute. Trying to access p.age after deletion results in an AttributeError.

4. Using @property to Compute a Derived Attribute

We can use @property to compute a value dynamically. Here, we define a class Temperature that converts between Celsius and Fahrenheit.

</>
Copy
class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius  # Private variable to store Celsius value

    @property
    def fahrenheit(self):
        """Converts Celsius to Fahrenheit dynamically."""
        return (self._celsius * 9/5) + 32

# Creating an instance of Temperature
temp = Temperature(25)

# Accessing the computed Fahrenheit value
print("Temperature in Fahrenheit:", temp.fahrenheit)

Output:

Temperature in Fahrenheit: 77.0

Since we did not define a setter, fahrenheit is a read-only property derived from the _celsius attribute.