@property

The `@property` [decorator](/tutorials/decorators) in Python provides a way to manage attributes in object-oriented programming. It allows you to define methods in a class that behave like attributes, providing getter, setter, and deleter functionality. This can lead to more concise and maintainable code by encapsulating accessor and mutator methods in a Pythonic way.

### Basic Usage of the `@property` Decorator

Let's start with a simple example to see how the `@property` decorator works.

#### Defining a Getter

Here is how you define a getter method using the `@property` decorator:

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

    @property
    def radius(self):
        return self._radius

# Example usage:
c = Circle(5)
print(c.radius)  # Output: 5
5
In this example:
- The `radius` method is decorated with `@property`, making it accessible like an attribute.
- We use an underscore `_radius` to indicate that it should be treated as a private attribute.

### Defining a Setter

You can define a setter method to update the property while maintaining control over how it is set.

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 cannot be negative")
        self._radius = value

# Example usage:
c = Circle(5)
print(c.radius)  # Output: 5
c.radius = 10
print(c.radius)  # Output: 10
# c.radius = -1  # Uncommenting this line will raise a ValueError
5
10
In this example:
- The `@property` decorator defines the getter.
- The `@radius.setter` decorator defines the setter, which includes validation logic to ensure the radius is non-negative.

### Defining a Deleter

You can also define a deleter method to delete the property.

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 cannot be negative")
        self._radius = value

    @radius.deleter
    def radius(self):
        del self._radius

# Example usage:
c = Circle(5)
print(c.radius)  # Output: 5
del c.radius
# print(c.radius)  # Uncommenting this line will raise an AttributeError
5
In this snippet:
- The `@radius.deleter` decorator defines the deleter method.

### Calculated Properties

The `@property` decorator is useful for calculated properties that are based on other attributes.

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 cannot be negative")
        self._radius = value

    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

# Example usage:
c = Circle(5)
print(c.radius)  # Output: 5
print(c.area)    # Output: 78.53981633974483 (π * 5^2)
5
78.53981633974483
The `area` property is calculated based on the `radius` attribute.

### Providing Read-Only Attributes

With the `@property` decorator, you can create read-only attributes by defining only the getter method.

class Person:
    def __init__(self, first_name, last_name):
        self._first_name = first_name
        self._last_name = last_name

    @property
    def full_name(self):
        return f"{self._first_name} {self._last_name}"

# Example usage:
p = Person("John", "Doe")
print(p.full_name)  # Output: John Doe
# p.full_name = "Jane Doe"  # Uncommenting this line will raise an AttributeError
John Doe
In this example, the `full_name` property is read-only because only a getter method is provided.

### Using `@property` with Dataclasses

If you are using Python's `dataclasses` module, you can combine it with `@property` to manage attributes.

from dataclasses import dataclass

@dataclass
class Rectangle:
    length: float
    width: float

    @property
    def area(self):
        return self.length * self.width

# Example usage:
r = Rectangle(5, 3)
print(r.area)  # Output: 15
15
Here, the `Rectangle` class is a dataclass, and the `area` is a calculated property.

### Conclusion

The `@property` decorator in Python provides a powerful and Pythonic way to manage attributes in classes. It allows you to encapsulate getter, setter, and deleter methods to create managed attributes. This leads to cleaner and more maintainable code, especially when dealing with complex logic for attribute access and modification.