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
- Getters: retrieve the value of an attribute, potentially performing calculations or applying logic in the process.
- Setters: allow for value assignment with optional validation or conversion.
- 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.
