In Python, descriptors and controlled attributes explain how to better frame objects

In Python, descriptors and controlled attributes explain how to better frame objects
2 min de lecture

Python descriptors allow you to control what happens when you read, modify, or delete an object attribute. A tutorial details how to use them to build three types of controlled attributes: validated, computed, and lazily loaded. It illustrates the approach with a fictional example of a tourism application in Japan.

A descriptor is a class that implements methods such as __get__, __set__, or __delete__, which define the behavior of an attribute. Not all are mandatory. The __set_name__ method, introduced in Python 3.6, tells the descriptor the name of the attribute it is associated with, allowing the value to be stored in the parent object’s __dict__ without name collisions. According to the author, this approach avoids cluttering the code with @property decorators, getters, and setters.

Validated, computed, or lazy attributes

The first example, named ValidatedRating, checks that a rating is a number (integer or decimal) between a minimum and maximum value, set by default to 1.0 and 5.0. If the value is non-numeric, it raises a TypeError; if the value is out of range, a ValueError.

The second, ComputedUSDPrice, is used to display in US dollars a price stored in Japanese yen (JPY). It calculates the conversion on the fly, using an exchange rate of 0.0067, indicated in the code as an approximation. The attribute being read-only, any attempt to write triggers an AttributeError.

The third, LazyItinerary, applies to data that is expensive to produce, such as reading a large JSON file or querying a database of Shinkansen (Japanese high-speed train) schedules. The descriptor only calculates the value on the first access, places it in the instance’s __dict__, and then removes itself. Subsequent accesses read this value directly. In the example, a one-second latency simulation is included.

Putting it into practice with a visit to Kyoto

These three descriptors are combined in a JapanTour class. When initialized with a destination “Kyoto”, a price of 45000 JPY, and a rating of 4.8, validation is triggered immediately. When the author then attempts to assign the value 6.0 to the rating, the exception is caught and the message “rating must be between 1.0 and 5.0.” is displayed.

The first access to the itinerary displays the simulated request message, then a three-day program: arrival and check-in in Kyoto, guided temple tour and street food tasting, then departure by Shinkansen. On the second access, the message no longer appears: the value is read directly from the instance.

The article concludes that these three types of descriptors strengthen the robustness of class attributes, ensuring data integrity, calculation of derived values, and management of expensive operations, respectively.