The @property decorator in Python is a powerful feature that allows you to define methods that behave like attributes. This can significantly enhance the readability and maintainability of your code.

Let’s dive into what @property is, how it works, and when to use it.

What is @property?

In Python, @property is a built-in decorator that transforms a method into a “getter” for a read-only attribute with the same name. It’s a pythonic way to use getters and setters without explicitly calling them as methods.

How does @property work?

The Python’s @property decorator is syntactic sugar for creating descriptor objects. Descriptors are the underlying mechanism that control attribute access in Python, defining special methods __get__, __set__, and __delete__.

The @property decorator creates a descriptor that wraps these methods, allowing us to define computed attributes with fine-grained control over getting, setting, and deleting.

Code without the @property decorator

 1class Car:
 2    def __init__(self, make, model, year):
 3        self._make = make  
 4        self._model = model
 5        self._year = year
 6        self._mileage = 0
 7
 8    def get_make(self):
 9        return self._make
10
11    def set_make(self, make):
12        self._make = make
13
14    def get_model(self):
15        return self._model
16
17    def set_model(self, model):
18        self._model = model
19
20    def get_year(self):
21        return self._year
22
23    def set_year(self, year):
24        self._year = year
25
26    def get_mileage(self):
27        return self._mileage
28
29    def set_mileage(self, mileage):
30        if mileage < self._mileage:
31            raise ValueError("Mileage can't be reduced")
32        self._mileage = mileage
33
34
35my_car = Car("Toyota", "Corolla", 2020)
36print(f"{my_car.get_year()=} {my_car.get_make()=} {my_car.get_model()=}")
37# my_car.get_year()=2020 my_car.get_make()='Toyota' my_car.get_model()='Corolla'
38my_car.set_mileage(5000)
39print(f"Current mileage: {my_car.get_mileage()}")
40# Current mileage: 5000

This code feels clunky and leas to more verbose code, especially if you have multiple attributes.

Code with the @property decorator

 1class Car:
 2    def __init__(self, make, model, year):
 3        self._make = make
 4        self._model = model
 5        self._year = year
 6        self._mileage = 0
 7
 8    @property
 9    def make(self):
10        return self._make
11
12    @property
13    def model(self):
14        return self._model
15
16    @property
17    def year(self):
18        return self._year
19
20    @property
21    def mileage(self):
22        return self._mileage
23
24    @mileage.setter
25    def mileage(self, value):
26        if value < self._mileage:
27            raise ValueError("Mileage can't be reduced")
28        self._mileage = value
29
30my_car = Car("Toyota", "Corolla", 2020)
31print(f"{my_car.year=} {my_car.make=} {my_car.model=}")
32# my_car.year=2020 my_car.make='Toyota' my_car.model='Corolla'
33my_car.mileage = 5000
34print(f"Current mileage: {my_car.mileage}")
35# Current mileage: 5000

Here in this example, the @property decorator was used to create three attributes make, model and year and defined as read-only attributes; moreover, mileage was used with a setter and validation. This code provides a clean interface to perform calculations or modifications when accessing these values.

Deletter, Setter, And Getter

  1. Getters: retrieve the value of an attribute, potentially performing calculations or applying logic in the process.
  2. Setters: allow for value assignment with optional validation or conversion.
  3. Deleters: less commonly used, provide a way to remove or reset a property, often performing cleanup tasks in the process.

Notes

The @property decorator in Python is a powerful tool that allows us to create smart attributes. As we’ve seen in these car-related examples, it can be used to:

  • Create read-only attributes
  • Implement validation when setting values
  • Compute values on-the-fly
  • Provide intuitive interfaces for different units of measurement

By using @property, we can write more pythonic code that is both easy to use and maintain. It allows us to encapsulate the internal workings of our classes while providing a clean and intuitive interface to the outside world.